Support custom repeater SSID without root
This commit is contained in:
@@ -5,14 +5,12 @@ import android.content.Intent
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.NetworkInfo
|
import android.net.NetworkInfo
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.*
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
|
||||||
import android.net.wifi.p2p.WifiP2pInfo
|
|
||||||
import android.net.wifi.p2p.WifiP2pManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.os.BuildCompat
|
import androidx.core.os.BuildCompat
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
@@ -33,7 +31,10 @@ import java.lang.reflect.InvocationTargetException
|
|||||||
class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "RepeaterService"
|
private const val TAG = "RepeaterService"
|
||||||
const val KEY_OPERATING_CHANNEL = "service.repeater.oc"
|
private const val KEY_NETWORK_NAME = "service.repeater.networkName"
|
||||||
|
private const val KEY_PASSPHRASE = "service.repeater.passphrase"
|
||||||
|
private const val KEY_OPERATING_BAND = "service.repeater.band"
|
||||||
|
private const val KEY_OPERATING_CHANNEL = "service.repeater.oc"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is only a "ServiceConnection" to system service and its impact on system is minimal.
|
* This is only a "ServiceConnection" to system service and its impact on system is minimal.
|
||||||
@@ -49,12 +50,23 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
|
|||||||
val supported get() = p2pManager != null
|
val supported get() = p2pManager != null
|
||||||
var persistentSupported = false
|
var persistentSupported = false
|
||||||
|
|
||||||
|
var networkName: String?
|
||||||
|
get() = app.pref.getString(KEY_NETWORK_NAME, null)
|
||||||
|
set(value) = app.pref.edit { putString(KEY_NETWORK_NAME, value) }
|
||||||
|
var passphrase: String?
|
||||||
|
get() = app.pref.getString(KEY_PASSPHRASE, null)
|
||||||
|
set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) }
|
||||||
|
var operatingBand: Int
|
||||||
|
get() = app.pref.getInt(KEY_OPERATING_BAND, 0)
|
||||||
|
set(value) = app.pref.edit { putInt(KEY_OPERATING_BAND, value) }
|
||||||
var operatingChannel: Int
|
var operatingChannel: Int
|
||||||
get() {
|
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()
|
set(value) = app.pref.edit { putString(RepeaterService.KEY_OPERATING_CHANNEL, value.toString()) }
|
||||||
|
|
||||||
|
private val networkNameField by lazy { WifiP2pConfig::class.java.getDeclaredField("networkName") }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
@@ -120,6 +132,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var routingManager: RoutingManager? = null
|
private var routingManager: RoutingManager? = null
|
||||||
|
private var persistNextGroup = false
|
||||||
|
|
||||||
var status = Status.IDLE
|
var status = Status.IDLE
|
||||||
private set(value) {
|
private set(value) {
|
||||||
@@ -245,12 +258,25 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
|
|||||||
* startService Step 2 (if a group isn't already available)
|
* startService Step 2 (if a group isn't already available)
|
||||||
*/
|
*/
|
||||||
private fun doStart() = try {
|
private fun doStart() = try {
|
||||||
p2pManager.createGroup(channel, object : WifiP2pManager.ActionListener {
|
val listener = object : WifiP2pManager.ActionListener {
|
||||||
override fun onFailure(reason: Int) {
|
override fun onFailure(reason: Int) {
|
||||||
startFailure(formatReason(R.string.repeater_create_group_failure, reason))
|
startFailure(formatReason(R.string.repeater_create_group_failure, reason))
|
||||||
}
|
}
|
||||||
override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3
|
override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3
|
||||||
})
|
}
|
||||||
|
val networkName = networkName
|
||||||
|
val passphrase = passphrase
|
||||||
|
if (!BuildCompat.isAtLeastQ() || networkName == null || passphrase == null) {
|
||||||
|
persistNextGroup = true
|
||||||
|
p2pManager.createGroup(channel, listener)
|
||||||
|
} else p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply {
|
||||||
|
setNetworkName("DIRECT-00-VPNHotspot") // placeholder for bypassing networkName check
|
||||||
|
setPassphrase(passphrase)
|
||||||
|
val frequency = operatingChannel
|
||||||
|
if (frequency == 0) setGroupOperatingBand(operatingBand) else setGroupOperatingFrequency(frequency)
|
||||||
|
}.build().apply {
|
||||||
|
networkNameField.set(this, networkName)
|
||||||
|
}, listener)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
startFailure(e.readableMessage)
|
startFailure(e.readableMessage)
|
||||||
@@ -276,6 +302,11 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
|
|||||||
*/
|
*/
|
||||||
private fun doStart(group: WifiP2pGroup) {
|
private fun doStart(group: WifiP2pGroup) {
|
||||||
binder.group = group
|
binder.group = group
|
||||||
|
if (persistNextGroup) {
|
||||||
|
networkName = group.networkName
|
||||||
|
passphrase = group.passphrase
|
||||||
|
persistNextGroup = false
|
||||||
|
}
|
||||||
check(routingManager == null)
|
check(routingManager == null)
|
||||||
routingManager = RoutingManager.LocalOnly(this, group.`interface`!!).apply { start() }
|
routingManager = RoutingManager.LocalOnly(this, group.`interface`!!).apply { start() }
|
||||||
status = Status.ACTIVE
|
status = Status.ACTIVE
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.content.Intent
|
|||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.wifi.WifiConfiguration
|
import android.net.wifi.WifiConfiguration
|
||||||
|
import android.net.wifi.p2p.WifiP2pConfig
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -32,6 +33,7 @@ import be.mygod.vpnhotspot.util.formatAddresses
|
|||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|
||||||
@@ -112,6 +114,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("No longer used since Android Q")
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
class ConfigHolder : ViewModel() {
|
class ConfigHolder : ViewModel() {
|
||||||
var config: P2pSupplicantConfiguration? = null
|
var config: P2pSupplicantConfiguration? = null
|
||||||
}
|
}
|
||||||
@@ -124,6 +128,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
private val data = Data()
|
private val data = Data()
|
||||||
internal var binder: RepeaterService.Binder? = null
|
internal var binder: RepeaterService.Binder? = null
|
||||||
private var p2pInterface: String? = null
|
private var p2pInterface: String? = null
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
private val holder = ViewModelProviders.of(parent).get<ConfigHolder>()
|
private val holder = ViewModelProviders.of(parent).get<ConfigHolder>()
|
||||||
|
|
||||||
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
override fun bindTo(viewHolder: RecyclerView.ViewHolder) {
|
||||||
@@ -153,25 +158,51 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
|
|
||||||
val configuration: WifiConfiguration? get() {
|
val configuration: WifiConfiguration? get() {
|
||||||
val group = binder?.group
|
if (BuildCompat.isAtLeastQ()) {
|
||||||
if (group != null) try {
|
val networkName = RepeaterService.networkName
|
||||||
val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress)
|
val passphrase = RepeaterService.passphrase
|
||||||
holder.config = config
|
if (networkName != null && passphrase != null) {
|
||||||
return newWifiApConfiguration(group.networkName, config.psk).apply {
|
return newWifiApConfiguration(networkName, passphrase).apply {
|
||||||
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used
|
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
apBand = when (RepeaterService.operatingBand) {
|
||||||
apBand = AP_BAND_ANY
|
WifiP2pConfig.GROUP_OWNER_BAND_AUTO -> AP_BAND_ANY
|
||||||
|
WifiP2pConfig.GROUP_OWNER_BAND_2GHZ -> AP_BAND_2GHZ
|
||||||
|
WifiP2pConfig.GROUP_OWNER_BAND_5GHZ -> AP_BAND_5GHZ
|
||||||
|
else -> throw IllegalArgumentException("Unknown operatingBand")
|
||||||
|
}
|
||||||
apChannel = RepeaterService.operatingChannel
|
apChannel = RepeaterService.operatingChannel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: RuntimeException) {
|
} else @Suppress("DEPRECATION") {
|
||||||
Timber.w(e)
|
val group = binder?.group
|
||||||
|
if (group != null) try {
|
||||||
|
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()
|
SmartSnackbar.make(R.string.repeater_configure_failure).show()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
fun updateConfiguration(config: WifiConfiguration) {
|
fun updateConfiguration(config: WifiConfiguration) {
|
||||||
holder.config?.let { master ->
|
if (BuildCompat.isAtLeastQ()) {
|
||||||
|
RepeaterService.networkName = config.SSID
|
||||||
|
RepeaterService.passphrase = config.preSharedKey
|
||||||
|
RepeaterService.operatingBand = when (config.apBand) {
|
||||||
|
AP_BAND_ANY -> WifiP2pConfig.GROUP_OWNER_BAND_AUTO
|
||||||
|
AP_BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ
|
||||||
|
AP_BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ
|
||||||
|
else -> throw IllegalArgumentException("Unknown apBand")
|
||||||
|
}
|
||||||
|
} else @Suppress("DEPRECATION") holder.config?.let { master ->
|
||||||
if (binder?.group?.networkName != config.SSID || master.psk != config.preSharedKey) try {
|
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
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.lang.IllegalStateException
|
|||||||
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488
|
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/d2986c2/wpa_supplicant/config.c#488
|
||||||
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182
|
* https://android.googlesource.com/platform/external/wpa_supplicant_8/+/6fa46df/wpa_supplicant/config_file.c#182
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("No longer used since Android Q")
|
||||||
class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress: String?) {
|
class P2pSupplicantConfiguration(private val group: WifiP2pGroup, ownerAddress: String?) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "P2pSupplicantConfiguration"
|
private const val TAG = "P2pSupplicantConfiguration"
|
||||||
|
|||||||
Reference in New Issue
Block a user