From 05c4ba5b818a145e78df2033f7ac96ac23922692 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 3 Jul 2020 10:55:21 +0800 Subject: [PATCH] Fix crashes --- .../java/be/mygod/librootkotlinx/RootServer.kt | 5 +++-- .../java/be/mygod/librootkotlinx/RootSession.kt | 9 +++++---- .../be/mygod/librootkotlinx/ServerCommands.kt | 2 +- mobile/src/main/java/be/mygod/vpnhotspot/App.kt | 2 +- .../java/be/mygod/vpnhotspot/RepeaterService.kt | 6 +++--- .../java/be/mygod/vpnhotspot/net/TetherType.kt | 2 +- .../be/mygod/vpnhotspot/net/TetheringManager.kt | 4 ++-- .../mygod/vpnhotspot/net/wifi/WifiApManager.kt | 9 +++++---- .../be/mygod/vpnhotspot/util/ConstantLookup.kt | 10 ++++++---- .../main/java/be/mygod/vpnhotspot/util/Utils.kt | 16 ++++++++++------ 10 files changed, 37 insertions(+), 28 deletions(-) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt index ee0e6fa4..2dce740d 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt @@ -193,13 +193,14 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U } try { callbackSpin() + if (active) throw RemoteException("Root process exited unexpectedly") } catch (e: Throwable) { process.destroy() throw e } finally { if (DEBUG) Log.d(TAG, "Waiting for exit") process.waitFor() - closeInternal(true) + withContext(NonCancellable) { closeInternal(true) } } check(process.errorStream.available() == 0) // stderr should not be used } @@ -280,7 +281,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U private suspend fun closeInternal(fromWorker: Boolean = false) = mutex.withLock { if (active) { active = false - if (DEBUG) Log.d(TAG, "Shutting down from client") + if (DEBUG) Log.d(TAG, if (fromWorker) "Shutting down from worker" else "Shutting down from client") try { sendLocked(Shutdown()) output.close() diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootSession.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootSession.kt index d2c7d24d..eba484e9 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootSession.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootSession.kt @@ -42,8 +42,9 @@ abstract class RootSession { } private suspend fun closeLocked() { + val server = server + this.server = null server?.close() - server = null } private fun startTimeoutLocked() { check(timeoutJob == null) @@ -51,8 +52,8 @@ abstract class RootSession { delay(timeout) mutex.withLock { check(usersCount == 0L) - closeLocked() timeoutJob = null + closeLocked() } } } @@ -75,14 +76,14 @@ abstract class RootSession { when { !server.active -> { usersCount = 0 - closeLocked() closePending = false + closeLocked() return@withLock } --usersCount > 0L -> return@withLock closePending -> { - closeLocked() closePending = false + closeLocked() } else -> startTimeoutLocked() } diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/ServerCommands.kt b/mobile/src/main/java/be/mygod/librootkotlinx/ServerCommands.kt index 7fbc318d..ccfa666e 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/ServerCommands.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/ServerCommands.kt @@ -39,7 +39,7 @@ interface RootCommandChannel : Parcelable { } @Parcelize -internal class CancelCommand(val index: Long) : RootCommandOneWay { +internal data class CancelCommand(val index: Long) : RootCommandOneWay { override suspend fun execute() = error("Internal implementation") } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index 68afcf95..9956fcb1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -68,7 +68,7 @@ class App : Application() { } else { if (priority >= Log.WARN || priority == Log.DEBUG) { Log.println(priority, tag, message) - Log.d(tag, message, t) + Log.w(tag, message, t) } if (priority >= Log.INFO && t !is NoShellException) { FirebaseCrashlytics.getInstance().recordException(t) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 64723eac..1fd25f01 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -76,7 +76,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) } var operatingBand: Int @SuppressLint("InlinedApi") - get() = app.pref.getInt(KEY_OPERATING_BAND, WifiP2pConfig.GROUP_OWNER_BAND_AUTO) + get() = app.pref.getInt(KEY_OPERATING_BAND, SoftApConfigurationCompat.BAND_ANY) set(value) = app.pref.edit { putInt(KEY_OPERATING_BAND, value) } var operatingChannel: Int get() { @@ -366,11 +366,11 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene setNetworkName(PLACEHOLDER_NETWORK_NAME) setPassphrase(passphrase) operatingChannel.let { oc -> - if (oc == 0) setGroupOperatingBand(when (operatingBand) { + if (oc == 0) setGroupOperatingBand(when (val band = operatingBand) { SoftApConfigurationCompat.BAND_ANY -> WifiP2pConfig.GROUP_OWNER_BAND_AUTO SoftApConfigurationCompat.BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ SoftApConfigurationCompat.BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ - else -> throw IllegalArgumentException("Unknown band") + else -> throw IllegalArgumentException("Unknown band $band") }) else setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc)) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt index 5b2b5287..c32a9299 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt @@ -67,7 +67,7 @@ enum class TetherType(@DrawableRes val icon: Int) { @RequiresApi(30) override fun onTetherableInterfaceRegexpsChanged(args: Array?) = synchronized(this) { if (requiresUpdate) return@synchronized - Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentToString()}") + Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}") TetheringManager.unregisterTetheringEventCallback(this) requiresUpdate = true listener() 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 0976c36b..208268c9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -605,12 +605,12 @@ object TetheringManager { */ fun getLastTetherError(iface: String): Int = getLastTetherError(Services.connectivity, iface) as Int - val tetherErrorLookup = ConstantLookup(clazz, "TETHER_ERROR_", + val tetherErrorLookup = ConstantLookup("TETHER_ERROR_", "TETHER_ERROR_NO_ERROR", "TETHER_ERROR_UNKNOWN_IFACE", "TETHER_ERROR_SERVICE_UNAVAIL", "TETHER_ERROR_UNSUPPORTED", "TETHER_ERROR_UNAVAIL_IFACE", "TETHER_ERROR_MASTER_ERROR", "TETHER_ERROR_TETHER_IFACE_ERROR", "TETHER_ERROR_UNTETHER_IFACE_ERROR", "TETHER_ERROR_ENABLE_NAT_ERROR", "TETHER_ERROR_DISABLE_NAT_ERROR", "TETHER_ERROR_IFACE_CFG_ERROR", "TETHER_ERROR_PROVISION_FAILED", - "TETHER_ERROR_DHCPSERVER_ERROR", "TETHER_ERROR_ENTITLEMENT_UNKNOWN") + "TETHER_ERROR_DHCPSERVER_ERROR", "TETHER_ERROR_ENTITLEMENT_UNKNOWN") { clazz } val Intent.tetheredIfaces get() = getStringArrayListExtra( if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 3bbfa7e1..80310d1c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -117,7 +117,7 @@ object WifiApManager { private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") } private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") } @RequiresApi(30) - val channelWidthLookup = ConstantLookup(classSoftApInfo, "CHANNEL_WIDTH_") + val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { classSoftApInfo } const val CHANNEL_WIDTH_INVALID = 0 private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") } @@ -125,15 +125,16 @@ object WifiApManager { private val areFeaturesSupported by lazy { classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java) } - @RequiresApi(30) - val featureLookup = LongConstantLookup(classSoftApCapability, "SOFTAP_FEATURE_") + @get:RequiresApi(30) + val featureLookup by lazy { LongConstantLookup(classSoftApCapability, "SOFTAP_FEATURE_") } + private val methods29 = setOf("onStateChanged", "onNumClientsChanged") @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?): Any? { - return if (Build.VERSION.SDK_INT >= 30) invokeActual(proxy, method, args) else { + return if (Build.VERSION.SDK_INT >= 30 || method.name !in methods29) invokeActual(proxy, method, args) else { executor.execute { invokeActual(proxy, method, args) } null // no return value as of API 30 } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt index 942a60ac..c7d3fb17 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt @@ -7,10 +7,11 @@ import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.R import timber.log.Timber -class ConstantLookup(private val clazz: Class<*>, private val prefix: String, private val lookup29: Array) { +class ConstantLookup(private val prefix: String, private val lookup29: Array, + private val clazz: () -> Class<*>) { private val lookup by lazy { SparseArrayCompat().apply { - for (field in clazz.declaredFields) try { + for (field in clazz().declaredFields) try { if (field.name.startsWith(prefix)) put(field.getInt(null), field.name) } catch (e: Exception) { Timber.w(e) @@ -30,10 +31,11 @@ class ConstantLookup(private val clazz: Class<*>, private val prefix: String, pr } @Suppress("FunctionName") -fun ConstantLookup(clazz: Class<*>, prefix: String, vararg lookup29: String) = ConstantLookup(clazz, prefix, lookup29) +fun ConstantLookup(prefix: String, vararg lookup29: String, clazz: () -> Class<*>) = + ConstantLookup(prefix, lookup29, clazz) @Suppress("FunctionName") inline fun ConstantLookup(prefix: String, vararg lookup29: String) = - ConstantLookup(T::class.java, prefix, lookup29) + ConstantLookup(prefix, lookup29) { T::class.java } class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) { private val lookup = LongSparseArray().apply { 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 b696673d..8aae58e6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -22,6 +22,7 @@ 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 timber.log.Timber import java.lang.invoke.MethodHandles import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationTargetException @@ -134,12 +135,15 @@ private val newLookup by lazy @TargetApi(26) { * * 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 +fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Method, args: Array?): Any? { + return if (Build.VERSION.SDK_INT >= 26 && method.isDefault) 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) + } else if (method.declaringClass === Object::class.java) { + // otherwise, we just redispatch it to InvocationHandler + if (args == null) method(this) else method(this, *args) + } else { + Timber.w("Unhandled method: $method(${args?.contentDeepToString()})") + null + } }