Support custom repeater SSID without root

This commit is contained in:
Mygod
2019-04-04 21:10:38 +08:00
parent 6b951519fe
commit e91abe0738
3 changed files with 82 additions and 19 deletions

View File

@@ -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

View File

@@ -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,6 +158,22 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
} }
val configuration: WifiConfiguration? get() { val configuration: WifiConfiguration? get() {
if (BuildCompat.isAtLeastQ()) {
val networkName = RepeaterService.networkName
val passphrase = RepeaterService.passphrase
if (networkName != null && passphrase != null) {
return newWifiApConfiguration(networkName, passphrase).apply {
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) // is not actually used
apBand = when (RepeaterService.operatingBand) {
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
}
}
} else @Suppress("DEPRECATION") {
val group = binder?.group val group = binder?.group
if (group != null) try { if (group != null) try {
val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress) val config = P2pSupplicantConfiguration(group, binder?.thisDevice?.deviceAddress)
@@ -167,11 +188,21 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
} catch (e: RuntimeException) { } catch (e: RuntimeException) {
Timber.w(e) 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

View File

@@ -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"