Correctly handle callSuper for Proxy

This commit is contained in:
Mygod
2020-05-31 07:40:25 +08:00
parent 3327242c2e
commit d17cd0bab3
3 changed files with 56 additions and 38 deletions

View File

@@ -18,6 +18,7 @@ import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.ensureReceiverUnregistered import be.mygod.vpnhotspot.util.ensureReceiverUnregistered
import com.android.dx.stock.ProxyBuilder import com.android.dx.stock.ProxyBuilder
import timber.log.Timber import timber.log.Timber
@@ -259,25 +260,22 @@ object TetheringManager {
build.invoke(builder) build.invoke(builder)
} }
val proxy = Proxy.newProxyInstance(interfaceStartTetheringCallback.classLoader, val proxy = Proxy.newProxyInstance(interfaceStartTetheringCallback.classLoader,
arrayOf(interfaceStartTetheringCallback)) { proxy, method, args -> arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
@Suppress("NAME_SHADOWING") val callback = reference.get() override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
when (val name = method.name) { @Suppress("NAME_SHADOWING") val callback = reference.get()
"onTetheringStarted" -> { return when (val name = method.name) {
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args") "onTetheringStarted" -> {
callback?.onTetheringStarted() if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args")
null callback?.onTetheringStarted()
} }
"onTetheringFailed" -> { "onTetheringFailed" -> {
if (args?.size != 1) Timber.w("Unexpected args for $name: $args") if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringFailed(args?.getOrNull(0) as? Int?) callback?.onTetheringFailed(args?.getOrNull(0) as? Int?)
null }
} else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
else -> {
Timber.w("Unexpected method, calling super: $method")
ProxyBuilder.callSuper(proxy, method, args)
} }
} }
} })
startTethering.invoke(instance, request, handler.makeExecutor(), proxy) startTethering.invoke(instance, request, handler.makeExecutor(), proxy)
return return
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
@@ -297,10 +295,7 @@ object TetheringManager {
callback?.onTetheringFailed() callback?.onTetheringFailed()
null null
} }
else -> { else -> ProxyBuilder.callSuper(proxy, method, args)
Timber.w("Unexpected method, calling super: $method")
ProxyBuilder.callSuper(proxy, method, args)
}
} }
} }
}.build() }.build()
@@ -460,7 +455,7 @@ object TetheringManager {
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
@Suppress("NAME_SHADOWING") val callback = reference.get() @Suppress("NAME_SHADOWING") val callback = reference.get()
val noArgs = args?.size ?: 0 val noArgs = args?.size ?: 0
when (val name = method.name) { return when (val name = method.name) {
"onTetheringSupported" -> { "onTetheringSupported" -> {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args") if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onTetheringSupported(args!![0] as Boolean) callback?.onTetheringSupported(args!![0] as Boolean)
@@ -495,12 +490,8 @@ object TetheringManager {
if (noArgs != 1) Timber.w("Unexpected args for $name: $args") if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
callback?.onOffloadStatusChanged(args!![0] as Int) callback?.onOffloadStatusChanged(args!![0] as Int)
} }
else -> { else -> callSuper(interfaceTetheringEventCallback, proxy, method, args)
Timber.w("Unexpected method, calling super: $method")
return ProxyBuilder.callSuper(proxy, method, args)
}
} }
return null
} }
}) })
} }

View File

@@ -5,8 +5,10 @@ import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pManager import android.net.wifi.p2p.WifiP2pManager
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import com.android.dx.stock.ProxyBuilder import be.mygod.vpnhotspot.util.callSuper
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
object WifiP2pManagerHelper { object WifiP2pManagerHelper {
@@ -90,17 +92,15 @@ object WifiP2pManagerHelper {
fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel, fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel,
listener: (Collection<WifiP2pGroup>) -> Unit) { listener: (Collection<WifiP2pGroup>) -> Unit) {
val proxy = Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, val proxy = Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
arrayOf(interfacePersistentGroupInfoListener)) { proxy, method, args -> arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
if (method.name == "onPersistentGroupInfoAvailable") { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) {
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) "onPersistentGroupInfoAvailable" -> {
@Suppress("UNCHECKED_CAST") if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
listener(getGroupList.invoke(args[0]) as Collection<WifiP2pGroup>) @Suppress("UNCHECKED_CAST") listener(getGroupList.invoke(args!![0]) as Collection<WifiP2pGroup>)
null
} else {
Timber.w(IllegalArgumentException("Unexpected method, calling super: $method"))
ProxyBuilder.callSuper(proxy, method, args)
}
} }
else -> callSuper(interfacePersistentGroupInfoListener, proxy, method, args)
}
})
requestPersistentGroupInfo.invoke(this, c, proxy) requestPersistentGroupInfo.invoke(this, c, proxy)
} }
} }

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.util package be.mygod.vpnhotspot.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.* import android.content.*
import android.net.InetAddresses import android.net.InetAddresses
import android.os.Build import android.os.Build
@@ -13,6 +14,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
@@ -21,6 +23,9 @@ import androidx.fragment.app.FragmentManager
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.net.MacAddressCompat
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import java.lang.invoke.MethodHandles
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.net.InetAddress import java.net.InetAddress
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.SocketException import java.net.SocketException
@@ -129,3 +134,25 @@ var MenuItem.isNotGone: Boolean
isVisible = value isVisible = value
isEnabled = value isEnabled = value
} }
@get:RequiresApi(26)
private val newLookup by lazy @TargetApi(26) {
MethodHandles.Lookup::class.java.getDeclaredConstructor(Class::class.java, Int::class.java).apply {
isAccessible = true
}
}
/**
* Call interface super method.
*
* See also: https://stackoverflow.com/a/49532463/2245107
*/
fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Method, args: Array<out Any?>?) = when {
Build.VERSION.SDK_INT >= 26 -> newLookup.newInstance(interfaceClass, 0xf) // ALL_MODES
.`in`(interfaceClass).unreflectSpecial(method, interfaceClass).bindTo(proxy).run {
if (args == null) invokeWithArguments() else invokeWithArguments(*args)
}
// only Java 8+ has default interface methods; otherwise, we just redispatch it to InvocationHandler
args == null -> method(this)
else -> method(this, *args)
}