Make InvocationHandler more robust

This commit is contained in:
Mygod
2021-07-25 00:27:50 -04:00
parent 0c36bcbade
commit 1d6fe3def1
8 changed files with 58 additions and 82 deletions

View File

@@ -71,9 +71,9 @@ enum class TetherType(@DrawableRes val icon: Int) {
}
@RequiresApi(30)
override fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) = synchronized(this) {
override fun onTetherableInterfaceRegexpsChanged(reg: Any?) = synchronized(this) {
if (requiresUpdate) return@synchronized
Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}")
Timber.i("onTetherableInterfaceRegexpsChanged: $reg")
TetheringManager.unregisterTetheringEventCallback(this)
requiresUpdate = true
listener()

View File

@@ -251,13 +251,12 @@ object TetheringManager {
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
dexCache(cacheDir)
handler { proxy, method, args ->
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
@Suppress("NAME_SHADOWING") val callback = reference.get()
when (method.name) {
"onTetheringStarted" -> callback?.onTetheringStarted()
"onTetheringFailed" -> callback?.onTetheringFailed()
else -> ProxyBuilder.callSuper(proxy, method, args)
if (args.isEmpty()) when (method.name) {
"onTetheringStarted" -> return@handler callback?.onTetheringStarted()
"onTetheringFailed" -> return@handler callback?.onTetheringFailed()
}
ProxyBuilder.callSuper(proxy, method, args)
}
}.build()
startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler)
@@ -279,15 +278,9 @@ object TetheringManager {
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") val callback = reference.get()
return when (val name = method.name) {
"onTetheringStarted" -> {
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringStarted()
}
"onTetheringFailed" -> {
if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringFailed(args?.get(0) as Int)
}
return when {
method.matches("onTetheringStarted") -> callback?.onTetheringStarted()
method.matches1<Int>("onTetheringFailed") -> callback?.onTetheringFailed(args?.get(0) as Int)
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
}
}
@@ -449,7 +442,7 @@ object TetheringManager {
* *@param reg The new regular expressions.
* @hide
*/
fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) {}
fun onTetherableInterfaceRegexpsChanged(reg: Any?) {}
/**
* Called when there was a change in the list of tetherable interfaces. Tetherable
@@ -545,40 +538,34 @@ object TetheringManager {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING")
val callback = reference.get()
val noArgs = args?.size ?: 0
return when (val name = method.name) {
"onTetheringSupported" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
return when {
method.matches1<Boolean>("onTetheringSupported") -> {
callback?.onTetheringSupported(args!![0] as Boolean)
}
"onUpstreamChanged" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
method.matches1<Network>("onUpstreamChanged") -> {
callback?.onUpstreamChanged(args!![0] as Network?)
}
"onTetherableInterfaceRegexpsChanged" -> {
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args)
method.name == "onTetherableInterfaceRegexpsChanged" &&
method.parameters.singleOrNull()?.type?.name ==
"android.net.TetheringManager\$TetheringInterfaceRegexps" -> {
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args!!.single())
regexpsSent = true
}
"onTetherableInterfacesChanged" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
method.matches1<java.util.List<*>>("onTetherableInterfacesChanged") -> {
@Suppress("UNCHECKED_CAST")
callback?.onTetherableInterfacesChanged(args!![0] as List<String?>)
}
"onTetheredInterfacesChanged" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
method.matches1<java.util.List<*>>("onTetheredInterfacesChanged") -> {
@Suppress("UNCHECKED_CAST")
callback?.onTetheredInterfacesChanged(args!![0] as List<String?>)
}
"onError" -> {
if (noArgs != 2) Timber.w("Unexpected args for $name: $args")
method.matches2<String, Int>("onError") -> {
callback?.onError(args!![0] as String, args[1] as Int)
}
"onClientsChanged" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
method.matches1<java.util.Collection<*>>("onClientsChanged") -> {
callback?.onClientsChanged(args!![0] as Collection<*>)
}
"onOffloadStatusChanged" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
method.matches1<Int>("onOffloadStatusChanged") -> {
callback?.onOffloadStatusChanged(args!![0] as Int)
}
else -> callSuper(interfaceTetheringEventCallback, proxy, method, args)

View File

@@ -8,7 +8,7 @@ import be.mygod.vpnhotspot.util.LongConstantLookup
@RequiresApi(30)
value class SoftApCapability(val inner: Parcelable) {
companion object {
private val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") }
val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") }
private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") }
private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) }
@get:RequiresApi(31)

View File

@@ -12,7 +12,7 @@ import timber.log.Timber
@RequiresApi(30)
value class SoftApInfo(val inner: Parcelable) {
companion object {
private val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") }
val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") }
private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") }
private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") }
@get:RequiresApi(31)

View File

@@ -10,13 +10,9 @@ import android.os.Build
import android.os.Handler
import android.os.Parcelable
import androidx.annotation.RequiresApi
import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
import be.mygod.vpnhotspot.util.ConstantLookup
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.findIdentifier
import be.mygod.vpnhotspot.util.*
import timber.log.Timber
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
@@ -260,55 +256,41 @@ object WifiApManager {
} else invokeActual(proxy, method, args)
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?.contentToString()}")
return when {
method.matches2<Int, Int>("onStateChanged") -> {
callback.onStateChanged(args!![0] as Int, args[1] as Int)
}
"onNumClientsChanged" -> @Suppress("DEPRECATION") {
method.matches1<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?.contentToString()}")
callback.onNumClientsChanged(args!![0] as Int)
}
"onConnectedClientsChanged" -> @TargetApi(30) {
method.matches1<java.util.List<*>>("onConnectedClientsChanged") -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged"))
@Suppress("UNCHECKED_CAST")
when (noArgs) {
1 -> callback.onConnectedClientsChanged(args!![0] as List<Parcelable>)
2 -> null // we use the old method which returns all clients in one call
else -> {
Timber.w("Unexpected args for $name: ${args?.contentToString()}")
null
}
}
callback.onConnectedClientsChanged(args!![0] as List<Parcelable>)
}
"onInfoChanged" -> @TargetApi(30) {
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
method.matches1<java.util.List<*>>("onInfoChanged") -> @TargetApi(31) {
if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+"))
@Suppress("UNCHECKED_CAST")
callback.onInfoChanged(args!![0] as List<Parcelable>)
}
method.matches("onInfoChanged", SoftApInfo.clazz) -> @TargetApi(30) {
when (Build.VERSION.SDK_INT) {
30 -> { }
in 31..Int.MAX_VALUE -> return null // ignore old version calls
else -> Timber.w(Exception("Unexpected onInfoChanged API 30"))
}
val arg = args!![0]
if (arg is List<*>) {
if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+"))
@Suppress("UNCHECKED_CAST")
callback.onInfoChanged(arg as List<Parcelable>)
} else {
when (Build.VERSION.SDK_INT) {
30 -> { }
in 31..Int.MAX_VALUE -> return null // ignore old version calls
else -> Timber.w(Exception("Unexpected onInfoChanged API 30"))
}
val info = SoftApInfo(arg as Parcelable)
callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID
if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg))
}
val info = SoftApInfo(arg as Parcelable)
callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID
if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg))
}
"onCapabilityChanged" -> @TargetApi(30) {
method.matches("onCapabilityChanged", SoftApCapability.clazz) -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged"))
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onCapabilityChanged(args!![0] as Parcelable)
}
"onBlockedClientConnecting" -> @TargetApi(30) {
method.matches("onBlockedClientConnecting", WifiClient.clazz, Int::class.java) -> @TargetApi(30) {
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting"))
if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int)
}
else -> callSuper(interfaceSoftApCallback, proxy, method, args)

View File

@@ -11,7 +11,7 @@ import timber.log.Timber
@RequiresApi(30)
value class WifiClient(val inner: Parcelable) {
companion object {
private val clazz by lazy { Class.forName("android.net.wifi.WifiClient") }
val clazz by lazy { Class.forName("android.net.wifi.WifiClient") }
private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") }
@get:RequiresApi(31)
private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }

View File

@@ -9,8 +9,8 @@ import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.matches1
import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
@@ -117,9 +117,8 @@ object WifiP2pManagerHelper {
val result = CompletableDeferred<Collection<WifiP2pGroup>>()
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) {
"onPersistentGroupInfoAvailable" -> {
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when {
method.matches1<java.util.Collection<*>>("onPersistentGroupInfoAvailable") -> {
@Suppress("UNCHECKED_CAST")
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
}

View File

@@ -53,6 +53,14 @@ fun Long.toPluralInt(): Int {
return (this % 1000000000).toInt() + 1000000000
}
@RequiresApi(26)
fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size &&
(0 until parameterCount).all { i -> parameters[i].type == classes[i] }
@RequiresApi(26)
inline fun <reified T> Method.matches1(name: String) = matches(name, T::class.java)
@RequiresApi(26)
inline fun <reified T0, reified T1> Method.matches2(name: String) = matches(name, T0::class.java, T1::class.java)
fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) {
try {
unregisterReceiver(receiver)