Initial support for registerSoftApCallback
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user