Support editing native Wi-Fi AP configurations
Support for repeater channel on Android 5 has been dropped because I am lazy.
This commit is contained in:
@@ -116,6 +116,9 @@ Undocumented API list:
|
|||||||
* (since API 24) [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112882)
|
* (since API 24) [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112882)
|
||||||
* (since API 24) [`Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112972)
|
* (since API 24) [`Landroid/net/ConnectivityManager;->startTethering(IZLandroid/net/ConnectivityManager$OnStartTetheringCallback;Landroid/os/Handler;)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112972)
|
||||||
* (since API 24) [`Landroid/net/ConnectivityManager;->stopTethering(I)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112974)
|
* (since API 24) [`Landroid/net/ConnectivityManager;->stopTethering(I)V,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#112974)
|
||||||
|
* [`Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#121357)
|
||||||
|
* [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#121416)
|
||||||
|
* (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pGroup;->getNetworkId()I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123194)
|
* [`Landroid/net/wifi/p2p/WifiP2pGroup;->getNetworkId()I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123194)
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/Collection;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123239)
|
* [`Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/Collection;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123239)
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123431)
|
* [`Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123431)
|
||||||
@@ -123,7 +126,6 @@ Undocumented API list:
|
|||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123458)
|
* [`Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123458)
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123459)
|
* [`Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#123459)
|
||||||
* [`Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#299587)
|
* [`Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/aa21a6e/appcompat/hiddenapi-flags.csv#299587)
|
||||||
* (deprecated since API 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z`
|
|
||||||
|
|
||||||
Undocumented system configurations:
|
Undocumented system configurations:
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.MANAGE_USB"
|
<uses-permission android:name="android.permission.MANAGE_USB"
|
||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
|
||||||
|
tools:ignore="ProtectedPermissions"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.TETHER_PRIVILEGED"
|
<uses-permission android:name="android.permission.TETHER_PRIVILEGED"
|
||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
|||||||
@@ -52,10 +52,12 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
|
|||||||
val supported get() = p2pManager != null
|
val supported get() = p2pManager != null
|
||||||
var persistentSupported = false
|
var persistentSupported = false
|
||||||
|
|
||||||
val operatingChannel: Int get() {
|
var operatingChannel: Int
|
||||||
|
get() {
|
||||||
val result = app.pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0
|
val result = app.pref.getString(KEY_OPERATING_CHANNEL, null)?.toIntOrNull() ?: 0
|
||||||
return if (result in 1..165) result else 0
|
return if (result in 1..165) result else 0
|
||||||
}
|
}
|
||||||
|
set(value) = app.pref.edit().putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ import android.content.ComponentName
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import android.text.Spanned
|
|
||||||
import android.text.style.TypefaceSpan
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
@@ -24,7 +20,6 @@ import be.mygod.vpnhotspot.DebugHelper
|
|||||||
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
@@ -76,15 +71,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager()
|
|||||||
private val lookup: Map<String, NetworkInterface> get() = parent.ifaceLookup
|
private val lookup: Map<String, NetworkInterface> get() = parent.ifaceLookup
|
||||||
|
|
||||||
override val icon get() = R.drawable.ic_action_perm_scan_wifi
|
override val icon get() = R.drawable.ic_action_perm_scan_wifi
|
||||||
override val title: CharSequence get() {
|
override val title: CharSequence get() = parent.getString(R.string.tethering_temp_hotspot)
|
||||||
val configuration = binder?.configuration ?: return parent.getString(R.string.tethering_temp_hotspot)
|
|
||||||
return SpannableStringBuilder("${configuration.SSID} - ").apply {
|
|
||||||
val start = length
|
|
||||||
append(configuration.preSharedKey)
|
|
||||||
setSpan(if (Build.VERSION.SDK_INT >= 28) TypefaceSpan(Typeface.MONOSPACE) else
|
|
||||||
TypefaceSpan("monospace"), start, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override val text: CharSequence get() {
|
override val text: CharSequence get() {
|
||||||
return lookup[binder?.iface ?: return ""]?.formatAddresses() ?: ""
|
return lookup[binder?.iface ?: return ""]?.formatAddresses() ?: ""
|
||||||
}
|
}
|
||||||
@@ -99,7 +86,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager()
|
|||||||
|
|
||||||
override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT
|
override val type get() = VIEW_TYPE_LOCAL_ONLY_HOTSPOT
|
||||||
private val data = Data()
|
private val data = Data()
|
||||||
private var binder: LocalOnlyHotspotService.Binder? = null
|
internal var binder: LocalOnlyHotspotService.Binder? = null
|
||||||
|
|
||||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||||
viewHolder as ViewHolder
|
viewHolder as ViewHolder
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@@ -21,10 +22,8 @@ import androidx.lifecycle.ViewModelProviders
|
|||||||
import androidx.lifecycle.get
|
import androidx.lifecycle.get
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.*
|
import be.mygod.vpnhotspot.*
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
import be.mygod.vpnhotspot.databinding.ListitemRepeaterBinding
|
||||||
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
|
import be.mygod.vpnhotspot.net.wifi.configuration.*
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pDialogFragment
|
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
@@ -51,7 +50,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
val ssid @Bindable get() = binder?.group?.networkName ?: ""
|
|
||||||
val addresses: CharSequence @Bindable get() {
|
val addresses: CharSequence @Bindable get() {
|
||||||
return try {
|
return try {
|
||||||
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
|
NetworkInterface.getByName(p2pInterface ?: return "")?.formatAddresses() ?: ""
|
||||||
@@ -59,12 +57,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var oc: CharSequence
|
|
||||||
@Bindable get() {
|
|
||||||
val oc = RepeaterService.operatingChannel
|
|
||||||
return if (oc in 1..165) oc.toString() else ""
|
|
||||||
}
|
|
||||||
set(value) = app.pref.edit().putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()).apply()
|
|
||||||
|
|
||||||
fun onStatusChanged() {
|
fun onStatusChanged() {
|
||||||
notifyPropertyChanged(BR.switchEnabled)
|
notifyPropertyChanged(BR.switchEnabled)
|
||||||
@@ -72,7 +64,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
notifyPropertyChanged(BR.addresses)
|
notifyPropertyChanged(BR.addresses)
|
||||||
}
|
}
|
||||||
fun onGroupChanged(group: WifiP2pGroup? = null) {
|
fun onGroupChanged(group: WifiP2pGroup? = null) {
|
||||||
notifyPropertyChanged(BR.ssid)
|
|
||||||
p2pInterface = group?.`interface`
|
p2pInterface = group?.`interface`
|
||||||
notifyPropertyChanged(BR.addresses)
|
notifyPropertyChanged(BR.addresses)
|
||||||
}
|
}
|
||||||
@@ -92,22 +83,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
fun wps() {
|
fun wps() {
|
||||||
if (binder?.active == true) WpsDialogFragment().show(parent, TetheringFragment.REPEATER_WPS)
|
if (binder?.active == true) WpsDialogFragment().show(parent, TetheringFragment.REPEATER_WPS)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun editConfigurations() {
|
|
||||||
val group = binder?.group
|
|
||||||
if (group != null) try {
|
|
||||||
val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress)
|
|
||||||
holder.config = config
|
|
||||||
WifiP2pDialogFragment().withArg(WifiP2pDialogFragment.Arg(WifiConfiguration().apply {
|
|
||||||
SSID = group.networkName
|
|
||||||
preSharedKey = config.psk
|
|
||||||
})).show(parent, TetheringFragment.REPEATER_EDIT_CONFIGURATION)
|
|
||||||
return
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
Timber.w(e)
|
|
||||||
}
|
|
||||||
SmartSnackbar.make(R.string.repeater_configure_failure).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -168,10 +143,27 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEditResult(data: Intent?) {
|
val configuration: WifiConfiguration? get() {
|
||||||
val master = holder.config ?: return
|
val group = binder?.group
|
||||||
try {
|
if (group != null) try {
|
||||||
val config = AlertDialogFragment.getRet<WifiP2pDialogFragment.Arg>(data!!).configuration
|
val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress)
|
||||||
|
holder.config = config
|
||||||
|
return newWifiApConfiguration(group.networkName, config.psk).apply {
|
||||||
|
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
apBand = AP_BAND_ANY
|
||||||
|
apChannel = RepeaterService.operatingChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
SmartSnackbar.make(R.string.repeater_configure_failure).show()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
fun updateConfiguration(config: WifiConfiguration) {
|
||||||
|
holder.config?.let { master ->
|
||||||
|
if (binder?.group?.networkName != config.SSID || master.psk != config.preSharedKey) try {
|
||||||
master.update(config.SSID, config.preSharedKey)
|
master.update(config.SSID, config.preSharedKey)
|
||||||
binder!!.group = null
|
binder!!.group = null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -180,4 +172,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
holder.config = null
|
holder.config = null
|
||||||
}
|
}
|
||||||
|
RepeaterService.operatingChannel = config.apChannel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
package be.mygod.vpnhotspot.manage
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.ComponentName
|
import android.content.*
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -23,24 +24,30 @@ import be.mygod.vpnhotspot.net.TetherType
|
|||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.configuration.WifiApDialogFragment
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
import be.mygod.vpnhotspot.util.isNotGone
|
import be.mygod.vpnhotspot.util.isNotGone
|
||||||
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|
||||||
class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClickListener {
|
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val START_LOCAL_ONLY_HOTSPOT = 1
|
const val START_LOCAL_ONLY_HOTSPOT = 1
|
||||||
const val REPEATER_EDIT_CONFIGURATION = 2
|
|
||||||
const val REPEATER_WPS = 3
|
const val REPEATER_WPS = 3
|
||||||
|
const val CONFIGURE_REPEATER = 2
|
||||||
|
const val CONFIGURE_AP = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
||||||
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
||||||
private val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
|
internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
|
||||||
private val tetherManagers by lazy @TargetApi(24) {
|
private val tetherManagers by lazy @TargetApi(24) {
|
||||||
listOf(TetherManager.Wifi(this@TetheringFragment),
|
listOf(TetherManager.Wifi(this@TetheringFragment),
|
||||||
TetherManager.Usb(this@TetheringFragment),
|
TetherManager.Usb(this@TetheringFragment),
|
||||||
@@ -100,13 +107,47 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic
|
|||||||
item.isNotGone = canMonitor.isNotEmpty()
|
item.isNotGone = canMonitor.isNotEmpty()
|
||||||
item.subMenu.apply {
|
item.subMenu.apply {
|
||||||
clear()
|
clear()
|
||||||
canMonitor.sorted().forEach { add(it).setOnMenuItemClickListener(this@TetheringFragment) }
|
for (iface in canMonitor.sorted()) add(iface).setOnMenuItemClickListener {
|
||||||
|
ContextCompat.startForegroundService(requireContext(), Intent(context, TetheringService::class.java)
|
||||||
|
.putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, iface))
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||||
ContextCompat.startForegroundService(requireContext(), Intent(context, TetheringService::class.java)
|
return when (item?.itemId) {
|
||||||
.putExtra(TetheringService.EXTRA_ADD_INTERFACE_MONITOR, item?.title ?: return false))
|
R.id.configuration -> item.subMenu.run {
|
||||||
return true
|
findItem(R.id.configuration_repeater).isNotGone = RepeaterService.supported
|
||||||
|
findItem(R.id.configuration_temp_hotspot).isNotGone =
|
||||||
|
adapter.localOnlyHotspotManager.binder?.configuration != null
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.configuration_repeater -> {
|
||||||
|
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(
|
||||||
|
adapter.repeaterManager.configuration ?: return false,
|
||||||
|
p2pMode = true
|
||||||
|
)).show(this, CONFIGURE_REPEATER)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.configuration_temp_hotspot -> {
|
||||||
|
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(
|
||||||
|
adapter.localOnlyHotspotManager.binder?.configuration ?: return false,
|
||||||
|
readOnly = true
|
||||||
|
)).show(this, 0) // read-only, no callback needed
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.configuration_ap -> try {
|
||||||
|
WifiApDialogFragment().withArg(WifiApDialogFragment.Arg(
|
||||||
|
WifiApManager.configuration
|
||||||
|
)).show(this, CONFIGURE_AP)
|
||||||
|
true
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
if (e.targetException !is SecurityException) Timber.w(e)
|
||||||
|
SmartSnackbar.make(e.targetException).show()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
@@ -117,13 +158,19 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic
|
|||||||
binding.interfaces.adapter = adapter
|
binding.interfaces.adapter = adapter
|
||||||
adapter.update(emptyList(), emptyList(), emptyList())
|
adapter.update(emptyList(), emptyList(), emptyList())
|
||||||
ServiceForegroundConnector(this, this, TetheringService::class)
|
ServiceForegroundConnector(this, this, TetheringService::class)
|
||||||
requireActivity().toolbar.inflateMenu(R.menu.toolbar_tethering)
|
requireActivity().toolbar.apply {
|
||||||
|
inflateMenu(R.menu.toolbar_tethering)
|
||||||
|
setOnMenuItemClickListener(this@TetheringFragment)
|
||||||
|
}
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
requireActivity().toolbar.menu.clear()
|
requireActivity().toolbar.apply {
|
||||||
|
menu.clear()
|
||||||
|
setOnMenuItemClickListener(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -131,11 +178,21 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic
|
|||||||
if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange()
|
if (Build.VERSION.SDK_INT >= 27) ManageBar.Data.notifyChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) = when (requestCode) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
val configuration by lazy { AlertDialogFragment.getRet<WifiApDialogFragment.Arg>(data!!).configuration }
|
||||||
|
when (requestCode) {
|
||||||
REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data)
|
REPEATER_WPS -> adapter.repeaterManager.onWpsResult(resultCode, data)
|
||||||
REPEATER_EDIT_CONFIGURATION -> adapter.repeaterManager.onEditResult(data)
|
CONFIGURE_REPEATER -> if (resultCode == DialogInterface.BUTTON_POSITIVE) {
|
||||||
|
adapter.repeaterManager.updateConfiguration(configuration)
|
||||||
|
}
|
||||||
|
CONFIGURE_AP -> if (resultCode == DialogInterface.BUTTON_POSITIVE) try {
|
||||||
|
WifiApManager.configuration = configuration
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
SmartSnackbar.make(R.string.configuration_rejected).show()
|
||||||
|
}
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
if (requestCode == START_LOCAL_ONLY_HOTSPOT) @TargetApi(26) {
|
if (requestCode == START_LOCAL_ONLY_HOTSPOT) @TargetApi(26) {
|
||||||
|
|||||||
@@ -3,15 +3,23 @@ package be.mygod.vpnhotspot.net.wifi
|
|||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
/**
|
|
||||||
* Although the functionalities were removed in API 26, it is already not functioning correctly on API 25.
|
|
||||||
*
|
|
||||||
* See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/
|
|
||||||
*/
|
|
||||||
object WifiApManager {
|
object WifiApManager {
|
||||||
private val setWifiApEnabled = WifiManager::class.java.getDeclaredMethod("setWifiApEnabled",
|
private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") }
|
||||||
|
private val setWifiApConfiguration by lazy {
|
||||||
|
WifiManager::class.java.getDeclaredMethod("setWifiApConfiguration", WifiConfiguration::class.java)
|
||||||
|
}
|
||||||
|
var configuration: WifiConfiguration
|
||||||
|
get() = getWifiApConfiguration.invoke(app.wifi) as WifiConfiguration
|
||||||
|
set(value) {
|
||||||
|
if (setWifiApConfiguration.invoke(app.wifi, value) as? Boolean != true) throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val setWifiApEnabled by lazy {
|
||||||
|
WifiManager::class.java.getDeclaredMethod("setWifiApEnabled",
|
||||||
WifiConfiguration::class.java, Boolean::class.java)
|
WifiConfiguration::class.java, Boolean::class.java)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Start AccessPoint mode with the specified
|
* Start AccessPoint mode with the specified
|
||||||
* configuration. If the radio is already running in
|
* configuration. If the radio is already running in
|
||||||
@@ -25,6 +33,11 @@ object WifiApManager {
|
|||||||
private fun WifiManager.setWifiApEnabled(wifiConfig: WifiConfiguration?, enabled: Boolean) =
|
private fun WifiManager.setWifiApEnabled(wifiConfig: WifiConfiguration?, enabled: Boolean) =
|
||||||
setWifiApEnabled.invoke(this, wifiConfig, enabled) as Boolean
|
setWifiApEnabled.invoke(this, wifiConfig, enabled) as Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Although the functionalities were removed in API 26, it is already not functioning correctly on API 25.
|
||||||
|
*
|
||||||
|
* See also: https://android.googlesource.com/platform/frameworks/base/+/5c0b10a4a9eecc5307bb89a271221f2b20448797%5E%21/
|
||||||
|
*/
|
||||||
@Deprecated("Not usable since API 26, malfunctioning on API 25")
|
@Deprecated("Not usable since API 26, malfunctioning on API 25")
|
||||||
fun start(wifiConfig: WifiConfiguration? = null) {
|
fun start(wifiConfig: WifiConfiguration? = null) {
|
||||||
app.wifi.isWifiEnabled = false
|
app.wifi.isWifiEnabled = false
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.net.wifi
|
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.net.wifi.WifiConfiguration
|
|
||||||
import android.net.wifi.WifiConfiguration.AuthAlgorithm
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import be.mygod.vpnhotspot.AlertDialogFragment
|
|
||||||
import be.mygod.vpnhotspot.R
|
|
||||||
import kotlinx.android.parcel.Parcelize
|
|
||||||
import kotlinx.android.synthetic.main.dialog_wifi_ap.view.*
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
|
|
||||||
*
|
|
||||||
* This dialog has been deprecated in API 28, but we are still using it since it works better for our purposes.
|
|
||||||
* Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea
|
|
||||||
*/
|
|
||||||
class WifiP2pDialogFragment : AlertDialogFragment<WifiP2pDialogFragment.Arg, WifiP2pDialogFragment.Arg>(), TextWatcher {
|
|
||||||
@Parcelize
|
|
||||||
data class Arg(val configuration: WifiConfiguration) : Parcelable
|
|
||||||
|
|
||||||
private lateinit var mView: View
|
|
||||||
private lateinit var mSsid: TextView
|
|
||||||
private lateinit var mPassword: EditText
|
|
||||||
override val ret: Arg? get() {
|
|
||||||
val config = WifiConfiguration()
|
|
||||||
config.SSID = mSsid.text.toString()
|
|
||||||
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN)
|
|
||||||
if (mPassword.length() != 0) {
|
|
||||||
val password = mPassword.text.toString()
|
|
||||||
config.preSharedKey = password
|
|
||||||
}
|
|
||||||
return Arg(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
|
||||||
mView = requireActivity().layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
|
|
||||||
setView(mView)
|
|
||||||
setTitle(R.string.repeater_configure)
|
|
||||||
mSsid = mView.ssid
|
|
||||||
mPassword = mView.password
|
|
||||||
setPositiveButton(context.getString(R.string.wifi_save), listener)
|
|
||||||
setNegativeButton(context.getString(R.string.wifi_cancel), null)
|
|
||||||
mSsid.text = arg.configuration.SSID
|
|
||||||
mSsid.addTextChangedListener(this@WifiP2pDialogFragment)
|
|
||||||
mPassword.setText(arg.configuration.preSharedKey)
|
|
||||||
mPassword.addTextChangedListener(this@WifiP2pDialogFragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validate() {
|
|
||||||
val mSsidString = mSsid.text.toString()
|
|
||||||
val ssidValid = mSsid.length() != 0 && Charset.forName("UTF-8").encode(mSsidString).limit() <= 32
|
|
||||||
val passwordValid = mPassword.length() >= 8
|
|
||||||
mView.password_wrapper.error =
|
|
||||||
if (passwordValid) null else requireContext().getString(R.string.credentials_password_too_short)
|
|
||||||
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = ssidValid && passwordValid
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
|
|
||||||
override fun afterTextChanged(editable: Editable) = validate()
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.mygod.vpnhotspot.net.wifi
|
package be.mygod.vpnhotspot.net.wifi.configuration
|
||||||
|
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi.configuration
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.net.wifi.WifiConfiguration
|
||||||
|
import android.net.wifi.WifiConfiguration.AuthAlgorithm
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import be.mygod.vpnhotspot.AlertDialogFragment
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.R
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.android.synthetic.main.dialog_wifi_ap.view.*
|
||||||
|
import java.lang.IllegalStateException
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
|
||||||
|
*
|
||||||
|
* This dialog has been deprecated in API 28, but we are still using it since it works better for our purposes.
|
||||||
|
* Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea
|
||||||
|
*/
|
||||||
|
class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), TextWatcher {
|
||||||
|
@Parcelize
|
||||||
|
data class Arg(val configuration: WifiConfiguration,
|
||||||
|
val readOnly: Boolean = false,
|
||||||
|
/**
|
||||||
|
* KeyMgmt is enforced to WPA_PSK.
|
||||||
|
* Various values for apBand are allowed according to different rules.
|
||||||
|
*/
|
||||||
|
val p2pMode: Boolean = false) : Parcelable
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
private sealed class BandOption {
|
||||||
|
open val apBand get() = AP_BAND_2GHZ
|
||||||
|
open val apChannel get() = 0
|
||||||
|
|
||||||
|
object BandAny : BandOption() {
|
||||||
|
override val apBand get() = AP_BAND_ANY
|
||||||
|
override fun toString() = app.getString(R.string.wifi_ap_choose_auto)
|
||||||
|
}
|
||||||
|
object Band2GHz : BandOption() {
|
||||||
|
override fun toString() = app.getString(R.string.wifi_ap_choose_2G)
|
||||||
|
}
|
||||||
|
object Band5GHz : BandOption() {
|
||||||
|
override val apBand get() = AP_BAND_5GHZ
|
||||||
|
override fun toString() = app.getString(R.string.wifi_ap_choose_5G)
|
||||||
|
}
|
||||||
|
class Channel(override val apChannel: Int) : BandOption() {
|
||||||
|
override fun toString() = "${channelToFrequency(apChannel)} MHz ($apChannel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var dialogView: View
|
||||||
|
override val ret: Arg? get() {
|
||||||
|
return Arg(WifiConfiguration().apply {
|
||||||
|
SSID = dialogView.ssid.text.toString()
|
||||||
|
allowedKeyManagement.set(
|
||||||
|
if (arg.p2pMode) WifiConfiguration.KeyMgmt.WPA_PSK else dialogView.security.selectedItemPosition)
|
||||||
|
allowedAuthAlgorithms.set(AuthAlgorithm.OPEN)
|
||||||
|
if (dialogView.password.length() != 0) preSharedKey = dialogView.password.text.toString()
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
val bandOption = dialogView.band.selectedItem as BandOption
|
||||||
|
apBand = bandOption.apBand
|
||||||
|
apChannel = bandOption.apChannel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||||
|
val activity = requireActivity()
|
||||||
|
dialogView = activity.layoutInflater.inflate(R.layout.dialog_wifi_ap, null)
|
||||||
|
setView(dialogView)
|
||||||
|
setTitle(R.string.configuration_view)
|
||||||
|
if (!arg.readOnly) setPositiveButton(R.string.wifi_save, listener)
|
||||||
|
setNegativeButton(R.string.donations__button_close, null)
|
||||||
|
dialogView.ssid.setText(arg.configuration.SSID)
|
||||||
|
if (!arg.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
|
if (arg.p2pMode) dialogView.security_wrapper.isGone = true else dialogView.security.apply {
|
||||||
|
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0,
|
||||||
|
WifiConfiguration.KeyMgmt.strings).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) =
|
||||||
|
throw IllegalStateException("Must select something")
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
dialogView.password_wrapper.isGone = position == WifiConfiguration.KeyMgmt.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val selected = arg.configuration.allowedKeyManagement.nextSetBit(0)
|
||||||
|
check(selected >= 0) { "No key management selected" }
|
||||||
|
check(arg.configuration.allowedKeyManagement.nextSetBit(selected + 1) < 0) {
|
||||||
|
"More than 1 key managements supplied"
|
||||||
|
}
|
||||||
|
setSelection(selected)
|
||||||
|
}
|
||||||
|
dialogView.password.setText(arg.configuration.preSharedKey)
|
||||||
|
if (!arg.readOnly) dialogView.password.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) dialogView.band.apply {
|
||||||
|
val options = mutableListOf<BandOption>().apply {
|
||||||
|
if (arg.p2pMode) add(BandOption.BandAny) else {
|
||||||
|
if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny)
|
||||||
|
add(BandOption.Band2GHz)
|
||||||
|
add(BandOption.Band5GHz)
|
||||||
|
}
|
||||||
|
addAll((1..165).map { BandOption.Channel(it) })
|
||||||
|
}
|
||||||
|
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, options).apply {
|
||||||
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
}
|
||||||
|
setSelection(if (arg.configuration.apChannel in 1..165) {
|
||||||
|
options.indexOfFirst { it.apChannel == arg.configuration.apChannel }
|
||||||
|
} else options.indexOfFirst { it.apBand == arg.configuration.apBand })
|
||||||
|
} else dialogView.band_wrapper.isGone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (!arg.readOnly) validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is reached only if not arg.readOnly.
|
||||||
|
*/
|
||||||
|
private fun validate() {
|
||||||
|
val ssidValid = dialogView.ssid.length() != 0 &&
|
||||||
|
Charset.forName("UTF-8").encode(dialogView.ssid.text.toString()).limit() <= 32
|
||||||
|
val passwordValid = when (dialogView.security.selectedItemPosition) {
|
||||||
|
WifiConfiguration.KeyMgmt.WPA_PSK, WPA2_PSK -> dialogView.password.length() >= 8
|
||||||
|
else -> true // do not try to validate
|
||||||
|
}
|
||||||
|
dialogView.password_wrapper.error = if (passwordValid) null else {
|
||||||
|
requireContext().getString(R.string.credentials_password_too_short)
|
||||||
|
}
|
||||||
|
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).isEnabled = ssidValid && passwordValid
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { }
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
|
||||||
|
override fun afterTextChanged(editable: Editable) = validate()
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi.configuration
|
||||||
|
|
||||||
|
import android.net.wifi.WifiConfiguration
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
|
val WPA2_PSK = WifiConfiguration.KeyMgmt.strings.indexOf("WPA2_PSK")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* apBand and apChannel is available since API 23.
|
||||||
|
*
|
||||||
|
* https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#242
|
||||||
|
*/
|
||||||
|
private val apBandField by lazy { WifiConfiguration::class.java.getDeclaredField("apBand") }
|
||||||
|
private val apChannelField by lazy { WifiConfiguration::class.java.getDeclaredField("apChannel") }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2GHz band.
|
||||||
|
*
|
||||||
|
* https://android.googlesource.com/platform/frameworks/base/+/android-7.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#241
|
||||||
|
*/
|
||||||
|
@RequiresApi(23)
|
||||||
|
const val AP_BAND_2GHZ = 0
|
||||||
|
/**
|
||||||
|
* 5GHz band.
|
||||||
|
*/
|
||||||
|
@RequiresApi(23)
|
||||||
|
const val AP_BAND_5GHZ = 1
|
||||||
|
/**
|
||||||
|
* Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
|
||||||
|
* operating country code and current radio conditions.
|
||||||
|
*
|
||||||
|
* Introduced in 9.0, but we will abuse this constant anyway.
|
||||||
|
* https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r1/wifi/java/android/net/wifi/WifiConfiguration.java#295
|
||||||
|
*/
|
||||||
|
@RequiresApi(23)
|
||||||
|
const val AP_BAND_ANY = -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The band which AP resides on
|
||||||
|
* -1:Any 0:2G 1:5G
|
||||||
|
* By default, 2G is chosen
|
||||||
|
*/
|
||||||
|
var WifiConfiguration.apBand: Int
|
||||||
|
@RequiresApi(23) get() = apBandField.get(this) as Int
|
||||||
|
@RequiresApi(23) set(value) = apBandField.set(this, value)
|
||||||
|
/**
|
||||||
|
* The channel which AP resides on
|
||||||
|
* 2G 1-11
|
||||||
|
* 5G 36,40,44,48,149,153,157,161,165
|
||||||
|
* 0 - find a random available channel according to the apBand
|
||||||
|
*/
|
||||||
|
var WifiConfiguration.apChannel: Int
|
||||||
|
@RequiresApi(23) get() = apChannelField.get(this) as Int
|
||||||
|
@RequiresApi(23) set(value) = apChannelField.set(this, value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The frequency which AP resides on (MHz). Resides in range [2412, 5815].
|
||||||
|
*/
|
||||||
|
fun channelToFrequency(channel: Int) = when (channel) {
|
||||||
|
in 1..14 -> 2407 + 5 * channel
|
||||||
|
in 15..165 -> 5000 + 5 * channel
|
||||||
|
else -> throw IllegalArgumentException("Invalid channel $channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on:
|
||||||
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88
|
||||||
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/wifi/tether/WifiTetherSettings.java#162
|
||||||
|
*/
|
||||||
|
fun newWifiApConfiguration(ssid: String, passphrase: String?) = try {
|
||||||
|
WifiApManager.configuration
|
||||||
|
} catch (e: InvocationTargetException) {
|
||||||
|
if (e.targetException !is SecurityException) Timber.w(e)
|
||||||
|
WifiConfiguration()
|
||||||
|
}.apply {
|
||||||
|
SSID = ssid
|
||||||
|
preSharedKey = passphrase
|
||||||
|
allowedKeyManagement.clear()
|
||||||
|
allowedAuthAlgorithms.clear()
|
||||||
|
allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package be.mygod.vpnhotspot.room
|
package be.mygod.vpnhotspot.room
|
||||||
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
|
import be.mygod.vpnhotspot.util.useParcel
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
@@ -10,27 +10,17 @@ import java.nio.ByteOrder
|
|||||||
object Converters {
|
object Converters {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun persistCharSequence(cs: CharSequence): ByteArray {
|
fun persistCharSequence(cs: CharSequence) = useParcel { p ->
|
||||||
val p = Parcel.obtain()
|
|
||||||
try {
|
|
||||||
TextUtils.writeToParcel(cs, p, 0)
|
TextUtils.writeToParcel(cs, p, 0)
|
||||||
return p.marshall()
|
p.marshall()
|
||||||
} finally {
|
|
||||||
p.recycle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun unpersistCharSequence(data: ByteArray): CharSequence {
|
fun unpersistCharSequence(data: ByteArray) = useParcel { p ->
|
||||||
val p = Parcel.obtain()
|
|
||||||
try {
|
|
||||||
p.unmarshall(data, 0, data.size)
|
p.unmarshall(data, 0, data.size)
|
||||||
p.setDataPosition(0)
|
p.setDataPosition(0)
|
||||||
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p)
|
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p)
|
||||||
} finally {
|
|
||||||
p.recycle()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package be.mygod.vpnhotspot.util
|
package be.mygod.vpnhotspot.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Parcel
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
@@ -15,7 +17,6 @@ import androidx.databinding.BindingAdapter
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.room.macToString
|
import be.mygod.vpnhotspot.room.macToString
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import java.lang.RuntimeException
|
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
@@ -32,6 +33,15 @@ fun Long.toPluralInt(): Int {
|
|||||||
return (this % 1000000000).toInt() + 1000000000
|
return (this % 1000000000).toInt() + 1000000000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("Recycle")
|
||||||
|
fun <T> useParcel(block: (Parcel) -> T) = Parcel.obtain().run {
|
||||||
|
try {
|
||||||
|
block(this)
|
||||||
|
} finally {
|
||||||
|
recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
|
fun broadcastReceiver(receiver: (Context, Intent) -> Unit) = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
override fun onReceive(context: Context, intent: Intent) = receiver(context, intent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#000000"
|
|
||||||
android:pathData="M17 16.99c-1.35 0-2.2 0.42 -2.95 0.8 -0.65 0.33 -1.18 0.6 -2.05 0.6 -0.9 0-1.4-0.25-2.05-0.6-0.75-0.38-1.57-0.8-2.95-0.8s-2.2 0.42 -2.95 0.8 c-0.65 0.33 -1.17 0.6 -2.05 0.6 v1.95c1.35 0 2.2-0.42 2.95-0.8 0.65 -0.33 1.17-0.6 2.05-0.6s1.4 0.25 2.05 0.6 c0.75 0.38 1.57 0.8 2.95 0.8 s2.2-0.42 2.95-0.8c0.65-0.33 1.18-0.6 2.05-0.6 0.9 0 1.4 0.25 2.05 0.6 0.75 0.38 1.58 0.8 2.95 0.8 v-1.95c-0.9 0-1.4-0.25-2.05-0.6-0.75-0.38-1.6-0.8-2.95-0.8zm0-4.45c-1.35 0-2.2 0.43 -2.95 0.8 -0.65 0.32 -1.18 0.6 -2.05 0.6 -0.9 0-1.4-0.25-2.05-0.6-0.75-0.38-1.57-0.8-2.95-0.8s-2.2 0.43 -2.95 0.8 c-0.65 0.32 -1.17 0.6 -2.05 0.6 v1.95c1.35 0 2.2-0.43 2.95-0.8 0.65 -0.35 1.15-0.6 2.05-0.6s1.4 0.25 2.05 0.6 c0.75 0.38 1.57 0.8 2.95 0.8 s2.2-0.43 2.95-0.8c0.65-0.35 1.15-0.6 2.05-0.6s1.4 0.25 2.05 0.6 c0.75 0.38 1.58 0.8 2.95 0.8 v-1.95c-0.9 0-1.4-0.25-2.05-0.6-0.75-0.38-1.6-0.8-2.95-0.8zm2.95-8.08c-0.75-0.38-1.58-0.8-2.95-0.8s-2.2 0.42 -2.95 0.8 c-0.65 0.32 -1.18 0.6 -2.05 0.6 -0.9 0-1.4-0.25-2.05-0.6-0.75-0.37-1.57-0.8-2.95-0.8s-2.2 0.42 -2.95 0.8 c-0.65 0.33 -1.17 0.6 -2.05 0.6 v1.93c1.35 0 2.2-0.43 2.95-0.8 0.65 -0.33 1.17-0.6 2.05-0.6s1.4 0.25 2.05 0.6 c0.75 0.38 1.57 0.8 2.95 0.8 s2.2-0.43 2.95-0.8c0.65-0.32 1.18-0.6 2.05-0.6 0.9 0 1.4 0.25 2.05 0.6 0.75 0.38 1.58 0.8 2.95 0.8 V5.04c-0.9 0-1.4-0.25-2.05-0.58zM17 8.09c-1.35 0-2.2 0.43 -2.95 0.8 -0.65 0.35 -1.15 0.6 -2.05 0.6 s-1.4-0.25-2.05-0.6c-0.75-0.38-1.57-0.8-2.95-0.8s-2.2 0.43 -2.95 0.8 c-0.65 0.35 -1.15 0.6 -2.05 0.6 v1.95c1.35 0 2.2-0.43 2.95-0.8 0.65 -0.32 1.18-0.6 2.05-0.6s1.4 0.25 2.05 0.6 c0.75 0.38 1.57 0.8 2.95 0.8 s2.2-0.43 2.95-0.8c0.65-0.32 1.18-0.6 2.05-0.6 0.9 0 1.4 0.25 2.05 0.6 0.75 0.38 1.58 0.8 2.95 0.8 V9.49c-0.9 0-1.4-0.25-2.05-0.6-0.75-0.38-1.6-0.8-2.95-0.8z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Source: https://android.googlesource.com/platform/packages/apps/Settings/+/6b4a31c/res/layout/wifi_ap_dialog.xml -->
|
<!-- Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/6b4a31c/res/layout/wifi_ap_dialog.xml -->
|
||||||
<!-- Copyright (C) 2010 The Android Open Source Project
|
<!-- Copyright (C) 2010 The Android Open Source Project
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
android:id="@+id/ssid_wrapper"
|
android:id="@+id/ssid_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dip"
|
android:layout_marginTop="8dip">
|
||||||
android:layout_marginBottom="8dip">
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/ssid"
|
android:id="@+id/ssid"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -37,10 +36,29 @@
|
|||||||
android:inputType="textMultiLine|textNoSuggestions"
|
android:inputType="textMultiLine|textNoSuggestions"
|
||||||
android:maxLength="32" />
|
android:maxLength="32" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/security_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_security" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/security"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:prompt="@string/wifi_security" />
|
||||||
|
</LinearLayout>
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/password_wrapper"
|
android:id="@+id/password_wrapper"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
app:passwordToggleEnabled="true"
|
app:passwordToggleEnabled="true"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
@@ -55,5 +73,23 @@
|
|||||||
android:maxLength="63"
|
android:maxLength="63"
|
||||||
android:imeOptions="flagForceAscii" />
|
android:imeOptions="flagForceAscii" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/band_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_hotspot_ap_band_title" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/band"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:prompt="@string/wifi_hotspot_ap_band_title" />
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -69,100 +69,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:nextFocusDown="@+id/oc"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:onClick="@{_ -> data.editConfigurations()}">
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="0dp"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:src="@drawable/ic_device_wifi_lock"
|
|
||||||
android:tint="?android:attr/textColorPrimary"/>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="0dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/wifi_ssid"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
|
||||||
|
|
||||||
<be.mygod.vpnhotspot.widget.AutoCollapseTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@{data.ssid}"
|
|
||||||
tools:text="…"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="0dp"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:src="@drawable/ic_content_wave"
|
|
||||||
android:tint="?android:attr/textColorPrimary"/>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="16dp"
|
|
||||||
android:layout_height="0dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/settings_service_repeater_oc"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/oc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@={data.oc}"
|
|
||||||
android:inputType="number"
|
|
||||||
android:imeOptions="actionDone"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:maxLength="3"
|
|
||||||
android:hint="@string/settings_service_repeater_oc_summary"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:focusable="true"
|
|
||||||
android:nextFocusUp="@+id/oc"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:onClick="@{_ -> data.wps()}"
|
android:onClick="@{_ -> data.wps()}"
|
||||||
|
|||||||
@@ -9,4 +9,18 @@
|
|||||||
app:showAsAction="always">
|
app:showAsAction="always">
|
||||||
<menu/>
|
<menu/>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
|
<item android:id="@+id/configuration"
|
||||||
|
android:icon="@drawable/ic_device_wifi_lock"
|
||||||
|
android:title="@string/configuration_view"
|
||||||
|
app:showAsAction="always">
|
||||||
|
<menu>
|
||||||
|
<item android:id="@+id/configuration_repeater"
|
||||||
|
android:title="@string/title_repeater"/>
|
||||||
|
<item android:id="@+id/configuration_temp_hotspot"
|
||||||
|
android:title="@string/tethering_temp_hotspot"/>
|
||||||
|
<item android:id="@+id/configuration_ap"
|
||||||
|
android:title="@string/tethering_manage_wifi"/>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!--
|
||||||
|
Values copied from:
|
||||||
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/419086d/res/values-ru/strings.xml
|
||||||
|
-->
|
||||||
|
|
||||||
<string name="repeater_configure">Настройка Wi-Fi ретранслятора</string>
|
|
||||||
<string name="repeater_configure_failure">Действительный конфиг не найден. Пожалуйста, сначала запустите ретранслятор.</string>
|
<string name="repeater_configure_failure">Действительный конфиг не найден. Пожалуйста, сначала запустите ретранслятор.</string>
|
||||||
<string name="repeater_clean_pog_failure">Не удалось удалить избыточную группу P2P (причина: %s)</string>
|
<string name="repeater_clean_pog_failure">Не удалось удалить избыточную группу P2P (причина: %s)</string>
|
||||||
|
|
||||||
@@ -15,7 +18,9 @@
|
|||||||
<string name="repeater_failure_reason_unsupported_operation">неподдерживаемая операция</string>
|
<string name="repeater_failure_reason_unsupported_operation">неподдерживаемая операция</string>
|
||||||
<string name="repeater_failure_disconnected">Сервис недоступен. Попробуйте позже</string>
|
<string name="repeater_failure_disconnected">Сервис недоступен. Попробуйте позже</string>
|
||||||
|
|
||||||
|
<string name="tethering_manage_usb" msgid="585829947108007917">"USB-модем"</string>
|
||||||
|
<string name="tethering_manage_wifi" msgid="7763495093333664887">"Точка доступа Wi‑Fi"</string>
|
||||||
|
<string name="tethering_manage_bluetooth" msgid="2379175828878753652">"Bluetooth-модем"</string>
|
||||||
|
|
||||||
<string name="connected_state_incomplete">" (подключение)"</string>
|
<string name="connected_state_incomplete">" (подключение)"</string>
|
||||||
<string name="connected_state_valid">" (доступный)"</string>
|
<string name="connected_state_valid">" (доступный)"</string>
|
||||||
@@ -37,7 +42,16 @@
|
|||||||
<string name="exception_interface_not_found">Ошибка: Нисходящий интерфейс не найден</string>
|
<string name="exception_interface_not_found">Ошибка: Нисходящий интерфейс не найден</string>
|
||||||
<string name="noisy_su_failure">Что-то пошло не так, пожалуйста, проверьте отладочную информацию.</string>
|
<string name="noisy_su_failure">Что-то пошло не так, пожалуйста, проверьте отладочную информацию.</string>
|
||||||
|
|
||||||
|
<string name="configuration_view">Настройка Wi-Fi ретранслятора</string>
|
||||||
|
<string name="wifi_ssid" msgid="5519636102673067319">"Имя сети"</string>
|
||||||
|
<string name="wifi_security" msgid="6603611185592956936">"Защита"</string>
|
||||||
|
<string name="wifi_password" msgid="5948219759936151048">"Пароль"</string>
|
||||||
<string name="credentials_password_too_short">Пароль должен содержать не менее 8 символов.</string>
|
<string name="credentials_password_too_short">Пароль должен содержать не менее 8 символов.</string>
|
||||||
|
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"Диапазон частот Wi-Fi"</string>
|
||||||
|
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"Авто"</string>
|
||||||
|
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2,4 ГГц"</string>
|
||||||
|
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5,0 ГГц"</string>
|
||||||
|
<string name="wifi_save" msgid="3331121567988522826">"Сохранить"</string>
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-ru/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-ru/donations__strings.xml -->
|
||||||
<string name="donations__button_close">Закрыть</string>
|
<string name="donations__button_close">Закрыть</string>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
<string name="repeater_wps_success_pbc">请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。</string>
|
<string name="repeater_wps_success_pbc">请在 2 分钟内在需要连接的设备上使用一键加密以连接到此中继。</string>
|
||||||
<string name="repeater_wps_success_keypad">成功注册 PIN。</string>
|
<string name="repeater_wps_success_keypad">成功注册 PIN。</string>
|
||||||
<string name="repeater_wps_failure">打开 WPS 失败(原因:%s)</string>
|
<string name="repeater_wps_failure">打开 WPS 失败(原因:%s)</string>
|
||||||
<string name="repeater_configure">设置 WLAN 中继</string>
|
|
||||||
<string name="repeater_configure_failure">未能找到有效的档案。请尝试先打开中继。</string>
|
<string name="repeater_configure_failure">未能找到有效的档案。请尝试先打开中继。</string>
|
||||||
<string name="repeater_clean_pog_failure">删除多余 P2P 群组失败(原因:%s)</string>
|
<string name="repeater_clean_pog_failure">删除多余 P2P 群组失败(原因:%s)</string>
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Values copied from:
|
Values copied from:
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/b63de87/res/values-zh-rCN/strings.xml
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/419086d/res/values-zh-rCN/strings.xml
|
||||||
* @string/usb_tethering_button_text
|
* @string/usb_tethering_button_text
|
||||||
* @string/wifi_hotspot_checkbox_text
|
* @string/wifi_hotspot_checkbox_text
|
||||||
* @string/bluetooth_tether_checkbox_text
|
* @string/bluetooth_tether_checkbox_text
|
||||||
@@ -83,8 +82,6 @@
|
|||||||
<string name="settings_service_masquerade_none">无</string>
|
<string name="settings_service_masquerade_none">无</string>
|
||||||
<string name="settings_service_masquerade_simple">简易</string>
|
<string name="settings_service_masquerade_simple">简易</string>
|
||||||
<string name="settings_service_masquerade_netd">Android Netd 服务</string>
|
<string name="settings_service_masquerade_netd">Android Netd 服务</string>
|
||||||
<string name="settings_service_repeater_oc">Wi\u2011Fi 运行频段 (不稳定)</string>
|
|
||||||
<string name="settings_service_repeater_oc_summary">"自动 (1\u201114 = 2.4GHz, 15\u2011165 = 5GHz)"</string>
|
|
||||||
<string name="settings_service_disable_ipv6">禁用 IPv6 共享</string>
|
<string name="settings_service_disable_ipv6">禁用 IPv6 共享</string>
|
||||||
<string name="settings_service_disable_ipv6_summary">防止 VPN 通过 IPv6 泄漏。</string>
|
<string name="settings_service_disable_ipv6_summary">防止 VPN 通过 IPv6 泄漏。</string>
|
||||||
<string name="settings_service_repeater_start_on_boot">开机自启动中继</string>
|
<string name="settings_service_repeater_start_on_boot">开机自启动中继</string>
|
||||||
@@ -128,11 +125,17 @@
|
|||||||
<string name="exception_interface_not_found">错误:未找到下游接口</string>
|
<string name="exception_interface_not_found">错误:未找到下游接口</string>
|
||||||
<string name="noisy_su_failure">发生异常,详情请查看调试信息。</string>
|
<string name="noisy_su_failure">发生异常,详情请查看调试信息。</string>
|
||||||
|
|
||||||
|
<string name="configuration_view">设置 WLAN 中继</string>
|
||||||
|
<string name="configuration_rejected">Android 系统拒绝使用此配置。(详情参见日志)</string>
|
||||||
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
|
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
|
||||||
|
<string name="wifi_security" msgid="6603611185592956936">"安全性"</string>
|
||||||
<string name="wifi_password" msgid="5948219759936151048">"密码"</string>
|
<string name="wifi_password" msgid="5948219759936151048">"密码"</string>
|
||||||
<string name="credentials_password_too_short" msgid="7502749986405522663">"密码至少应包含 8 个字符。"</string>
|
<string name="credentials_password_too_short" msgid="7502749986405522663">"密码至少应包含 8 个字符。"</string>
|
||||||
|
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"AP 频段"</string>
|
||||||
|
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"自动"</string>
|
||||||
|
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2.4 GHz 频段"</string>
|
||||||
|
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5.0 GHz 频段"</string>
|
||||||
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
|
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
|
||||||
<string name="wifi_cancel" msgid="6763568902542968964">"取消"</string>
|
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
||||||
<string name="donations__button_close">关闭</string>
|
<string name="donations__button_close">关闭</string>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
<!--
|
<!--
|
||||||
Values copied from:
|
Values copied from:
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/7686ef8/res/xml/tether_prefs.xml
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/e5ed810/res/values/strings.xml
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/5697a7e/res/values/strings.xml
|
||||||
* @string/wifi_tether_configure_ap_text
|
|
||||||
* @string/usb_tethering_button_text
|
* @string/usb_tethering_button_text
|
||||||
* @string/wifi_hotspot_checkbox_text
|
* @string/wifi_hotspot_checkbox_text
|
||||||
* @string/bluetooth_tether_checkbox_text
|
* @string/bluetooth_tether_checkbox_text
|
||||||
@@ -22,7 +21,6 @@
|
|||||||
device.</string>
|
device.</string>
|
||||||
<string name="repeater_wps_success_keypad">PIN registered.</string>
|
<string name="repeater_wps_success_keypad">PIN registered.</string>
|
||||||
<string name="repeater_wps_failure">Failed to start WPS (reason: %s)</string>
|
<string name="repeater_wps_failure">Failed to start WPS (reason: %s)</string>
|
||||||
<string name="repeater_configure">Configure Wi\u2011Fi repeater</string>
|
|
||||||
<string name="repeater_configure_failure">Valid config not found. Please start repeater first.</string>
|
<string name="repeater_configure_failure">Valid config not found. Please start repeater first.</string>
|
||||||
<string name="repeater_clean_pog_failure">Failed to remove redundant P2P group (reason: %s)</string>
|
<string name="repeater_clean_pog_failure">Failed to remove redundant P2P group (reason: %s)</string>
|
||||||
|
|
||||||
@@ -89,8 +87,6 @@
|
|||||||
<string name="settings_service_masquerade_none">None</string>
|
<string name="settings_service_masquerade_none">None</string>
|
||||||
<string name="settings_service_masquerade_simple">Simple</string>
|
<string name="settings_service_masquerade_simple">Simple</string>
|
||||||
<string name="settings_service_masquerade_netd">Android Netd Service</string>
|
<string name="settings_service_masquerade_netd">Android Netd Service</string>
|
||||||
<string name="settings_service_repeater_oc">Operating Wi\u2011Fi channel (unstable)</string>
|
|
||||||
<string name="settings_service_repeater_oc_summary">Auto (1\u201114 = 2.4GHz, 15\u2011165 = 5GHz)</string>
|
|
||||||
<string name="settings_service_disable_ipv6">Disable IPv6 tethering</string>
|
<string name="settings_service_disable_ipv6">Disable IPv6 tethering</string>
|
||||||
<string name="settings_service_disable_ipv6_summary">Enabling this option will prevent VPN leaks via IPv6.</string>
|
<string name="settings_service_disable_ipv6_summary">Enabling this option will prevent VPN leaks via IPv6.</string>
|
||||||
<string name="settings_service_repeater_start_on_boot">Start repeater on boot</string>
|
<string name="settings_service_repeater_start_on_boot">Start repeater on boot</string>
|
||||||
@@ -137,11 +133,17 @@
|
|||||||
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
<string name="exception_interface_not_found">Fatal: Downstream interface not found</string>
|
||||||
<string name="noisy_su_failure">Something went wrong, please check the debug information.</string>
|
<string name="noisy_su_failure">Something went wrong, please check the debug information.</string>
|
||||||
|
|
||||||
|
<string name="configuration_view">Wi\u2011Fi configuration</string>
|
||||||
|
<string name="configuration_rejected">Android system refuses such configuration. (see logcat)</string>
|
||||||
<string name="wifi_ssid">Network name</string>
|
<string name="wifi_ssid">Network name</string>
|
||||||
|
<string name="wifi_security">Security</string>
|
||||||
<string name="wifi_password">Password</string>
|
<string name="wifi_password">Password</string>
|
||||||
<string name="credentials_password_too_short">The password must have at least 8 characters.</string>
|
<string name="credentials_password_too_short">The password must have at least 8 characters.</string>
|
||||||
|
<string name="wifi_hotspot_ap_band_title">AP Band</string>
|
||||||
|
<string name="wifi_ap_choose_auto">Auto</string>
|
||||||
|
<string name="wifi_ap_choose_2G">2.4 GHz Band</string>
|
||||||
|
<string name="wifi_ap_choose_5G">5.0 GHz Band</string>
|
||||||
<string name="wifi_save">Save</string>
|
<string name="wifi_save">Save</string>
|
||||||
<string name="wifi_cancel">Cancel</string>
|
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->
|
||||||
<string name="donations__button_close">Close</string>
|
<string name="donations__button_close">Close</string>
|
||||||
|
|||||||
@@ -21,6 +21,18 @@
|
|||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
<item name="android:gravity">start</item>
|
<item name="android:gravity">start</item>
|
||||||
</style>
|
</style>
|
||||||
|
<style name="wifi_item_label">
|
||||||
|
<item name="android:paddingStart">8dip</item>
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
|
<item name="android:textAppearance">@android:style/TextAppearance.Material.Body1</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorSecondary</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_item_content">
|
||||||
|
<item name="android:textAlignment">viewStart</item>
|
||||||
|
<item name="android:textAppearance">@android:style/TextAppearance.Material.Subhead</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
|
</style>
|
||||||
<style name="wifi_item_edit_content">
|
<style name="wifi_item_edit_content">
|
||||||
<item name="android:paddingStart">4dip</item>
|
<item name="android:paddingStart">4dip</item>
|
||||||
<item name="android:layout_marginStart">4dip</item>
|
<item name="android:layout_marginStart">4dip</item>
|
||||||
|
|||||||
Reference in New Issue
Block a user