Initial support for registerSoftApCallback

This commit is contained in:
Mygod
2020-07-03 07:38:51 +08:00
parent fbb1483969
commit 798275e9c9
10 changed files with 399 additions and 44 deletions

View File

@@ -3,13 +3,25 @@ package be.mygod.vpnhotspot.net.wifi
import android.annotation.TargetApi
import android.content.Intent
import android.content.pm.PackageManager
import android.net.MacAddress
import android.net.wifi.SoftApConfiguration
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi
import androidx.collection.SparseArrayCompat
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.makeExecutor
import timber.log.Timber
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.Executor
object WifiApManager {
/**
@@ -48,9 +60,153 @@ object WifiApManager {
setWifiApConfiguration(Services.wifi, value.toWifiConfiguration())
} else setSoftApConfiguration(Services.wifi, value.toPlatform())) as Boolean
@RequiresApi(28)
interface SoftApCallbackCompat {
/**
* Called when soft AP state changes.
*
* @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
* {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
* {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
* @param failureReason reason when in failed state. One of
* {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
*/
fun onStateChanged(state: Int, failureReason: Int) { }
/**
* Called when number of connected clients to soft AP changes.
*
* @param numClients number of connected clients
*/
@Deprecated("onConnectedClientsChanged")
fun onNumClientsChanged(numClients: Int) { }
@RequiresApi(30)
fun onConnectedClientsChanged(clients: List<MacAddress>) {
@Suppress("DEPRECATION")
onNumClientsChanged(clients.size)
}
@RequiresApi(30)
fun onInfoChanged(frequency: Int, bandwidth: Int) { }
@RequiresApi(30)
fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) { }
@RequiresApi(30)
fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { }
}
private val startFailures29 = arrayOf("SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
private val startFailures by lazy {
SparseArrayCompat<String>().apply {
for (field in WifiManager::class.java.declaredFields) try {
// all SAP_START_FAILURE_* are system-api since API 30
if (field.name.startsWith("SAP_START_FAILURE_")) put(field.get(null) as Int, field.name)
} catch (e: Exception) {
Timber.w(e)
}
}
}
fun failureReason(reason: Int): String {
if (Build.VERSION.SDK_INT >= 30) try {
startFailures.get(reason)?.let { return it }
} catch (e: ReflectiveOperationException) {
Timber.w(e)
}
return startFailures29.getOrNull(reason) ?: app.getString(R.string.failure_reason_unknown, reason)
}
private val interfaceSoftApCallback by lazy { Class.forName("android.net.wifi.WifiManager\$SoftApCallback") }
private val registerSoftApCallback by lazy {
val parameters = if (Build.VERSION.SDK_INT >= 30) {
arrayOf(Executor::class.java, interfaceSoftApCallback)
} else arrayOf(interfaceSoftApCallback, Handler::class.java)
WifiManager::class.java.getDeclaredMethod("registerSoftApCallback", *parameters)
}
private val unregisterSoftApCallback by lazy {
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
}
private val getMacAddress by lazy {
Class.forName("android.net.wifi.WifiClient").getDeclaredMethod("getMacAddress")
}
private val classSoftApInfo by lazy { Class.forName("android.net.wifi.SoftApInfo") }
private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") }
private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") }
private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") }
private val getMaxSupportedClients by lazy { classSoftApCapability.getDeclaredMethod("getMaxSupportedClients") }
private val areFeaturesSupported by lazy {
classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java)
}
@RequiresApi(28)
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
arrayOf(interfaceSoftApCallback), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
return if (Build.VERSION.SDK_INT >= 30) invokeActual(proxy, method, args) else {
executor.execute { invokeActual(proxy, method, args) }
null // no return value as of API 30
}
}
private fun invokeActual(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
val noArgs = args?.size ?: 0
return when (val name = method.name) {
"onStateChanged" -> {
if (noArgs != 2) Timber.w("Unexpected args for $name: $args")
callback.onStateChanged(args!![0] as Int, args[1] as Int)
}
"onNumClientsChanged" -> @Suppress("DEPRECATION") {
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback.onNumClientsChanged(args!![0] as Int)
}
"onConnectedClientsChanged" -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback.onConnectedClientsChanged((args!![0] as Iterable<*>)
.map { getMacAddress(it) as MacAddress })
}
"onInfoChanged" -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onInfoChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
val softApInfo = args!![0]
callback.onInfoChanged(getFrequency(softApInfo) as Int, getBandwidth(softApInfo) as Int)
}
"onCapabilityChanged" -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
val softApCapability = args!![0]
var supportedFeatures = 0L
var probe = 1L
while (probe != 0L) {
if (areFeaturesSupported(softApCapability, probe) as Boolean) {
supportedFeatures = supportedFeatures or probe
}
probe += probe
}
callback.onCapabilityChanged(getMaxSupportedClients(softApCapability) as Int, supportedFeatures)
}
"onBlockedClientConnecting" -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting"))
if (noArgs != 2) Timber.w("Unexpected args for $name: $args")
callback.onBlockedClientConnecting(getMacAddress(args!![0]) as MacAddress, args[1] as Int)
}
else -> callSuper(interfaceSoftApCallback, proxy, method, args)
}
}
})
if (Build.VERSION.SDK_INT >= 30) {
registerSoftApCallback(Services.wifi, executor, proxy)
} else registerSoftApCallback(Services.wifi, proxy, null)
return proxy
}
@RequiresApi(28)
fun unregisterSoftApCallback(key: Any) = unregisterSoftApCallback(Services.wifi, key)
private val cancelLocalOnlyHotspotRequest by lazy {
WifiManager::class.java.getDeclaredMethod("cancelLocalOnlyHotspotRequest")
}
@RequiresApi(26)
fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(Services.wifi)
@Suppress("DEPRECATION")