Implement (un)registerTetheringEventCallback
This commit is contained in:
@@ -4,6 +4,8 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.LinkAddress
|
||||
import android.net.MacAddress
|
||||
import android.net.Network
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -79,6 +81,13 @@ object TetheringManager {
|
||||
*/
|
||||
const val EXTRA_ERRORED_TETHER = "erroredArray"
|
||||
|
||||
/** Tethering offload status is stopped. */
|
||||
const val TETHER_HARDWARE_OFFLOAD_STOPPED = 0
|
||||
/** Tethering offload status is started. */
|
||||
const val TETHER_HARDWARE_OFFLOAD_STARTED = 1
|
||||
/** Fail to start tethering offload. */
|
||||
const val TETHER_HARDWARE_OFFLOAD_FAILED = 2
|
||||
|
||||
// tethering types supported by enableTetheringInternal: https://android.googlesource.com/platform/frameworks/base/+/5d36f01/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java#549
|
||||
/**
|
||||
* Wifi tethering type.
|
||||
@@ -289,6 +298,256 @@ object TetheringManager {
|
||||
stopTetheringLegacy.invoke(app.connectivity, type)
|
||||
}
|
||||
|
||||
data class TetheredClient(val underlying: Any) {
|
||||
@get:RequiresApi(30)
|
||||
val macAddress get() = getMacAddress.invoke(underlying) as MacAddress
|
||||
@get:RequiresApi(30)
|
||||
val addresses get() = (getAddresses.invoke(underlying) as Iterable<*>).map { inner ->
|
||||
getAddress.invoke(inner) as LinkAddress to getHostname.invoke(inner) as String?
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
val tetheringType get() = getTetheringType.invoke(underlying) as Int
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for use with [registerTetheringEventCallback] to find out tethering
|
||||
* upstream status.
|
||||
*/
|
||||
interface TetheringEventCallback {
|
||||
/**
|
||||
* Called when tethering supported status changed.
|
||||
*
|
||||
* This will be called immediately after the callback is registered, and may be called
|
||||
* multiple times later upon changes.
|
||||
*
|
||||
* Tethering may be disabled via system properties, device configuration, or device
|
||||
* policy restrictions.
|
||||
*
|
||||
* @param supported The new supported status
|
||||
*/
|
||||
fun onTetheringSupported(supported: Boolean) {}
|
||||
|
||||
/**
|
||||
* Called when tethering upstream changed.
|
||||
*
|
||||
* This will be called immediately after the callback is registered, and may be called
|
||||
* multiple times later upon changes.
|
||||
*
|
||||
* @param network the [Network] of tethering upstream. Null means tethering doesn't
|
||||
* have any upstream.
|
||||
*/
|
||||
fun onUpstreamChanged(network: Network?) {}
|
||||
|
||||
/**
|
||||
* Called when there was a change in tethering interface regular expressions.
|
||||
*
|
||||
* This will be called immediately after the callback is registered, and may be called
|
||||
* multiple times later upon changes.
|
||||
*
|
||||
* *@param reg The new regular expressions.
|
||||
* @param tetherableBluetoothRegexs an array of 0 or more regular expression Strings defining
|
||||
* what interfaces are considered tetherable bluetooth interfaces.
|
||||
* @param tetherableUsbRegexs an array of 0 or more regular expression Strings defining
|
||||
* what interfaces are considered tetherable usb interfaces.
|
||||
* @param tetherableWifiRegexs an array of 0 or more regular expression Strings defining
|
||||
* what interfaces are considered tetherable wifi interfaces.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
fun onTetherableInterfaceRegexpsChanged(tetherableBluetoothRegexs: List<String?>,
|
||||
tetherableUsbRegexs: List<String?>,
|
||||
tetherableWifiRegexs: List<String?>) {}
|
||||
|
||||
/**
|
||||
* Called when there was a change in the list of tetherable interfaces. Tetherable
|
||||
* interface means this interface is available and can be used for tethering.
|
||||
*
|
||||
* This will be called immediately after the callback is registered, and may be called
|
||||
* multiple times later upon changes.
|
||||
* @param interfaces The list of tetherable interface names.
|
||||
*/
|
||||
fun onTetherableInterfacesChanged(interfaces: List<String?>) {}
|
||||
|
||||
/**
|
||||
* Called when there was a change in the list of tethered interfaces.
|
||||
*
|
||||
* This will be called immediately after the callback is registered, and may be called
|
||||
* multiple times later upon changes.
|
||||
* @param interfaces The list of 0 or more String of currently tethered interface names.
|
||||
*/
|
||||
fun onTetheredInterfacesChanged(interfaces: List<String?>) {}
|
||||
|
||||
/**
|
||||
* Called when an error occurred configuring tethering.
|
||||
*
|
||||
* This will be called immediately after the callback is registered if the latest status
|
||||
* on the interface is an error, and may be called multiple times later upon changes.
|
||||
* @param ifName Name of the interface.
|
||||
* @param error One of `TetheringManager#TETHER_ERROR_*`.
|
||||
*/
|
||||
fun onError(ifName: String, error: Int) {}
|
||||
|
||||
/**
|
||||
* Called when the list of tethered clients changes.
|
||||
*
|
||||
* This callback provides best-effort information on connected clients based on state
|
||||
* known to the system, however the list cannot be completely accurate (and should not be
|
||||
* used for security purposes). For example, clients behind a bridge and using static IP
|
||||
* assignments are not visible to the tethering device; or even when using DHCP, such
|
||||
* clients may still be reported by this callback after disconnection as the system cannot
|
||||
* determine if they are still connected.
|
||||
* @param clients The new set of tethered clients; the collection is not ordered.
|
||||
*/
|
||||
fun onClientsChanged(clients: List<TetheredClient?>) {}
|
||||
|
||||
/**
|
||||
* Called when tethering offload status changes.
|
||||
*
|
||||
* This will be called immediately after the callback is registered.
|
||||
* @param status The offload status.
|
||||
*/
|
||||
fun onOffloadStatusChanged(status: Int) {}
|
||||
}
|
||||
|
||||
@get:RequiresApi(30)
|
||||
private val classTetheredClient by lazy { Class.forName("android.net.TetheredClient") }
|
||||
@get:RequiresApi(30)
|
||||
private val getMacAddress by lazy { classTetheredClient.getDeclaredMethod("getMacAddress") }
|
||||
@get:RequiresApi(30)
|
||||
private val getAddresses by lazy { classTetheredClient.getDeclaredMethod("getAddresses") }
|
||||
@get:RequiresApi(30)
|
||||
private val getTetheringType by lazy { classTetheredClient.getDeclaredMethod("getTetheringType") }
|
||||
|
||||
@get:RequiresApi(30)
|
||||
private val classTetheringInterfaceRegexps by lazy {
|
||||
Class.forName("android.net.TetheredClient\$TetheringInterfaceRegexps")
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val getTetherableBluetoothRegexs by lazy {
|
||||
classTetheringInterfaceRegexps.getDeclaredMethod("getTetherableBluetoothRegexs")
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val getTetherableUsbRegexs by lazy {
|
||||
classTetheringInterfaceRegexps.getDeclaredMethod("getTetherableUsbRegexs")
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val getTetherableWifiRegexs by lazy {
|
||||
classTetheringInterfaceRegexps.getDeclaredMethod("getTetherableWifiRegexs")
|
||||
}
|
||||
|
||||
@get:RequiresApi(30)
|
||||
private val classAddressInfo by lazy { Class.forName("android.net.TetheredClient\$AddressInfo") }
|
||||
@get:RequiresApi(30)
|
||||
private val getAddress by lazy { classAddressInfo.getDeclaredMethod("getAddress") }
|
||||
@get:RequiresApi(30)
|
||||
private val getHostname by lazy { classAddressInfo.getDeclaredMethod("getHostname") }
|
||||
|
||||
@get:RequiresApi(30)
|
||||
private val interfaceTetheringEventCallback by lazy {
|
||||
Class.forName("android.net.TetheredClient\$TetheringEventCallback")
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val registerTetheringEventCallback by lazy {
|
||||
clazz.getDeclaredMethod("registerTetheringEventCallback", Executor::class.java, interfaceTetheringEventCallback)
|
||||
}
|
||||
@get:RequiresApi(30)
|
||||
private val unregisterTetheringEventCallback by lazy {
|
||||
clazz.getDeclaredMethod("unregisterTetheringEventCallback", interfaceTetheringEventCallback)
|
||||
}
|
||||
|
||||
private val callbackMap = mutableMapOf<TetheringEventCallback, Any>()
|
||||
/**
|
||||
* Start listening to tethering change events. Any new added callback will receive the last
|
||||
* tethering status right away. If callback is registered,
|
||||
* [TetheringEventCallback.onUpstreamChanged] will immediately be called. If tethering
|
||||
* has no upstream or disabled, the argument of callback will be null. The same callback object
|
||||
* cannot be registered twice.
|
||||
*
|
||||
* Requires TETHER_PRIVILEGED or ACCESS_NETWORK_STATE.
|
||||
*
|
||||
* @param executor the executor on which callback will be invoked.
|
||||
* @param callback the callback to be called when tethering has change events.
|
||||
*/
|
||||
@RequiresApi(30)
|
||||
fun registerTetheringEventCallback(executor: Executor, callback: TetheringEventCallback) {
|
||||
val reference = WeakReference(callback)
|
||||
val proxy = synchronized(callbackMap) {
|
||||
callbackMap.computeIfAbsent(callback) {
|
||||
Proxy.newProxyInstance(interfaceTetheringEventCallback.classLoader,
|
||||
arrayOf(interfaceTetheringEventCallback)) { proxy, method, args ->
|
||||
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
||||
when (val name = method.name) {
|
||||
"onTetheringSupported" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
callback?.onTetheringSupported(args[0] as Boolean)
|
||||
null
|
||||
}
|
||||
"onUpstreamChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
callback?.onUpstreamChanged(args[0] as Network?)
|
||||
null
|
||||
}
|
||||
"onTetherableInterfaceRegexpsChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
val reg = args[0]
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
callback?.onTetherableInterfaceRegexpsChanged(
|
||||
getTetherableBluetoothRegexs.invoke(reg) as List<String?>,
|
||||
getTetherableUsbRegexs.invoke(reg) as List<String?>,
|
||||
getTetherableWifiRegexs.invoke(reg) as List<String?>)
|
||||
null
|
||||
}
|
||||
"onTetherableInterfacesChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
callback?.onTetherableInterfacesChanged(args[0] as List<String?>)
|
||||
null
|
||||
}
|
||||
"onTetheredInterfacesChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
callback?.onTetheredInterfacesChanged(args[0] as List<String?>)
|
||||
null
|
||||
}
|
||||
"onError" -> {
|
||||
if (args.size > 2) Timber.w("Unexpected args for $name: $args")
|
||||
callback?.onError(args[0] as String, args[1] as Int)
|
||||
null
|
||||
}
|
||||
"onClientsChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
callback?.onClientsChanged((args[0] as Iterable<*>).map { TetheredClient(it!!) })
|
||||
null
|
||||
}
|
||||
"onOffloadStatusChanged" -> {
|
||||
if (args.size > 1) Timber.w("Unexpected args for $name: $args")
|
||||
callback?.onOffloadStatusChanged(args[0] as Int)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
Timber.w("Unexpected method, calling super: $method")
|
||||
ProxyBuilder.callSuper(proxy, method, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
registerTetheringEventCallback.invoke(instance, executor, proxy)
|
||||
}
|
||||
/**
|
||||
* Remove tethering event callback previously registered with
|
||||
* [registerTetheringEventCallback].
|
||||
*
|
||||
* Requires TETHER_PRIVILEGED or ACCESS_NETWORK_STATE.
|
||||
*
|
||||
* @param callback previously registered callback.
|
||||
*/
|
||||
@RequiresApi(30)
|
||||
fun unregisterTetheringEventCallback(callback: TetheringEventCallback) {
|
||||
val proxy = synchronized(callbackMap) { callbackMap.remove(callback) } ?: return
|
||||
unregisterTetheringEventCallback.invoke(instance, proxy)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a more detailed error code after a Tethering or Untethering
|
||||
* request asynchronously failed.
|
||||
|
||||
Reference in New Issue
Block a user