diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 931bffeb..d70aab38 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -18,6 +18,7 @@ import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.util.broadcastReceiver +import be.mygod.vpnhotspot.util.callSuper import be.mygod.vpnhotspot.util.ensureReceiverUnregistered import com.android.dx.stock.ProxyBuilder import timber.log.Timber @@ -259,25 +260,22 @@ object TetheringManager { build.invoke(builder) } val proxy = Proxy.newProxyInstance(interfaceStartTetheringCallback.classLoader, - arrayOf(interfaceStartTetheringCallback)) { proxy, method, args -> - @Suppress("NAME_SHADOWING") val callback = reference.get() - when (val name = method.name) { - "onTetheringStarted" -> { - if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringStarted() - null - } - "onTetheringFailed" -> { - if (args?.size != 1) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringFailed(args?.getOrNull(0) as? Int?) - null - } - else -> { - Timber.w("Unexpected method, calling super: $method") - ProxyBuilder.callSuper(proxy, method, args) + arrayOf(interfaceStartTetheringCallback), object : InvocationHandler { + override fun invoke(proxy: Any, method: Method, args: Array?): 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?.getOrNull(0) as? Int?) + } + else -> callSuper(interfaceStartTetheringCallback, proxy, method, args) } } - } + }) startTethering.invoke(instance, request, handler.makeExecutor(), proxy) return } catch (e: InvocationTargetException) { @@ -297,10 +295,7 @@ object TetheringManager { callback?.onTetheringFailed() null } - else -> { - Timber.w("Unexpected method, calling super: $method") - ProxyBuilder.callSuper(proxy, method, args) - } + else -> ProxyBuilder.callSuper(proxy, method, args) } } }.build() @@ -460,7 +455,7 @@ object TetheringManager { override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @Suppress("NAME_SHADOWING") val callback = reference.get() val noArgs = args?.size ?: 0 - when (val name = method.name) { + return when (val name = method.name) { "onTetheringSupported" -> { if (noArgs != 1) Timber.w("Unexpected args for $name: $args") callback?.onTetheringSupported(args!![0] as Boolean) @@ -495,12 +490,8 @@ object TetheringManager { if (noArgs != 1) Timber.w("Unexpected args for $name: $args") callback?.onOffloadStatusChanged(args!![0] as Int) } - else -> { - Timber.w("Unexpected method, calling super: $method") - return ProxyBuilder.callSuper(proxy, method, args) - } + else -> callSuper(interfaceTetheringEventCallback, proxy, method, args) } - return null } }) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt index e0d6398a..5d697a8e 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt @@ -5,8 +5,10 @@ import android.net.wifi.WpsInfo import android.net.wifi.p2p.WifiP2pGroup import android.net.wifi.p2p.WifiP2pManager import be.mygod.vpnhotspot.App.Companion.app -import com.android.dx.stock.ProxyBuilder +import be.mygod.vpnhotspot.util.callSuper import timber.log.Timber +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method import java.lang.reflect.Proxy object WifiP2pManagerHelper { @@ -90,17 +92,15 @@ object WifiP2pManagerHelper { fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel, listener: (Collection) -> Unit) { val proxy = Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, - arrayOf(interfacePersistentGroupInfoListener)) { proxy, method, args -> - if (method.name == "onPersistentGroupInfoAvailable") { - if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) - @Suppress("UNCHECKED_CAST") - listener(getGroupList.invoke(args[0]) as Collection) - null - } else { - Timber.w(IllegalArgumentException("Unexpected method, calling super: $method")) - ProxyBuilder.callSuper(proxy, method, args) - } + arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler { + override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when (method.name) { + "onPersistentGroupInfoAvailable" -> { + if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) + @Suppress("UNCHECKED_CAST") listener(getGroupList.invoke(args!![0]) as Collection) } + else -> callSuper(interfacePersistentGroupInfoListener, proxy, method, args) + } + }) requestPersistentGroupInfo.invoke(this, c, proxy) } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 967ba260..10387731 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -1,6 +1,7 @@ package be.mygod.vpnhotspot.util import android.annotation.SuppressLint +import android.annotation.TargetApi import android.content.* import android.net.InetAddresses import android.os.Build @@ -13,6 +14,7 @@ import android.view.MenuItem import android.view.View import android.widget.ImageView import androidx.annotation.DrawableRes +import androidx.annotation.RequiresApi import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.databinding.BindingAdapter @@ -21,6 +23,9 @@ import androidx.fragment.app.FragmentManager import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.MacAddressCompat 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.NetworkInterface import java.net.SocketException @@ -129,3 +134,25 @@ var MenuItem.isNotGone: Boolean isVisible = 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?) = 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) +}