diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 84d3d425..4170bcd9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -27,6 +27,7 @@ import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetheringManager +import be.mygod.vpnhotspot.net.wifi.SoftApCapability import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat import be.mygod.vpnhotspot.net.wifi.SoftApInfo import be.mygod.vpnhotspot.net.wifi.WifiApManager @@ -41,6 +42,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber import java.lang.reflect.InvocationTargetException +import java.util.* sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), TetheringManager.StartTetheringCallback { @@ -145,7 +147,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), private var failureReason: Int? = null private var numClients: Int? = null private var info = emptyList() - private var capability: Pair? = null + private var capability: Parcelable? = null init { if (Build.VERSION.SDK_INT >= 28) parent.viewLifecycleOwner.lifecycle.addObserver(this) @@ -176,8 +178,8 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), this.info = info data.notifyChange() } - override fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) { - capability = maxSupportedClients to supportedFeatures + override fun onCapabilityChanged(capability: Parcelable) { + this.capability = capability data.notifyChange() } override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { @@ -193,6 +195,22 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), override val title get() = parent.getString(R.string.tethering_manage_wifi) override val tetherType get() = TetherType.WIFI override val type get() = VIEW_TYPE_WIFI + + @TargetApi(30) + private fun formatCapability(locale: Locale) = capability?.let { + val capability = SoftApCapability(it) + val maxClients = capability.maxSupportedClients + val supportedFeatures = capability.supportedFeatures + app.resources.getQuantityText(R.plurals.tethering_manage_wifi_capabilities, maxClients).format(locale, + numClients ?: "?", maxClients, sequence { + var features = supportedFeatures + if (features != 0L) while (features != 0L) { + val bit = features.takeLowestOneBit() + yield(SoftApCapability.featureLookup(bit, true)) + features = features and bit.inv() + } else yield(parent.getText(R.string.tethering_manage_wifi_no_features)) + }.joinToSpanned()) + } override val text get() = parent.resources.configuration.locale.let { locale -> listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) }, baseError, info.run { if (isEmpty()) null else joinToSpanned("\n") @TargetApi(30) { parcel -> @@ -215,17 +233,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), } else parent.getText(R.string.tethering_manage_wifi_info).format(locale, frequency, channel, bandwidth) } - }, capability?.let { (maxSupportedClients, supportedFeatures) -> - app.resources.getQuantityText(R.plurals.tethering_manage_wifi_capabilities, - maxSupportedClients).format(locale, numClients ?: "?", maxSupportedClients, sequence { - var features = supportedFeatures - if (features != 0L) while (features != 0L) { - val bit = features.takeLowestOneBit() - yield(WifiApManager.featureLookup(bit, true)) - features = features and bit.inv() - } else yield(parent.getText(R.string.tethering_manage_wifi_no_features)) - }.joinToSpanned()) - }).joinToSpanned("\n") + }, formatCapability(locale)).joinToSpanned("\n") } override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt new file mode 100644 index 00000000..85d0c057 --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt @@ -0,0 +1,27 @@ +package be.mygod.vpnhotspot.net.wifi + +import android.os.Parcelable +import androidx.annotation.RequiresApi +import be.mygod.vpnhotspot.util.LongConstantLookup + +@JvmInline +@RequiresApi(30) +value class SoftApCapability(val inner: Parcelable) { + companion object { + private 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) } + val featureLookup by lazy { LongConstantLookup(clazz, "SOFTAP_FEATURE_") } + } + + val maxSupportedClients get() = getMaxSupportedClients.invoke(inner) as Int + val supportedFeatures: Long get() { + var supportedFeatures = 0L + var probe = 1L + while (probe != 0L) { + if (areFeaturesSupported(inner, probe) as Boolean) supportedFeatures = supportedFeatures or probe + probe += probe + } + return supportedFeatures + } +} 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 565c2eb5..7d9354cf 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 @@ -14,7 +14,6 @@ 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.LongConstantLookup import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.callSuper import timber.log.Timber @@ -91,7 +90,7 @@ object WifiApManager { fun onInfoChanged(info: List) { } @RequiresApi(30) - fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) { } + fun onCapabilityChanged(capability: Parcelable) { } @RequiresApi(30) fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { } @@ -117,14 +116,6 @@ object WifiApManager { Class.forName("android.net.wifi.WifiClient").getDeclaredMethod("getMacAddress") } - private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") } - private val getMaxSupportedClients by lazy { classSoftApCapability.getDeclaredMethod("getMaxSupportedClients") } - private val areFeaturesSupported by lazy { - classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java) - } - @get:RequiresApi(30) - val featureLookup by lazy { LongConstantLookup(classSoftApCapability, "SOFTAP_FEATURE_") } - @RequiresApi(28) fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any { val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader, @@ -183,16 +174,7 @@ object WifiApManager { "onCapabilityChanged" -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged")) if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") - val softApCapability = args!![0] - var supportedFeatures = 0L - var probe = 1L - while (probe != 0L) { - if (areFeaturesSupported(softApCapability, probe) as Boolean) { - supportedFeatures = supportedFeatures or probe - } - probe += probe - } - callback.onCapabilityChanged(getMaxSupportedClients(softApCapability) as Int, supportedFeatures) + callback.onCapabilityChanged(args!![0] as Parcelable) } "onBlockedClientConnecting" -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting")) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt index 2883bf47..b3d72dfc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt @@ -43,10 +43,9 @@ object WifiApCommands { } @Parcelize @RequiresApi(30) - data class OnCapabilityChanged(val maxSupportedClients: Int, - val supportedFeatures: Long) : SoftApCallbackParcel() { + data class OnCapabilityChanged(val capability: Parcelable) : SoftApCallbackParcel() { override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) = - callback.onCapabilityChanged(maxSupportedClients, supportedFeatures) + callback.onCapabilityChanged(capability) } @Parcelize @RequiresApi(30) @@ -79,8 +78,8 @@ object WifiApCommands { @RequiresApi(30) override fun onInfoChanged(info: List) = push(SoftApCallbackParcel.OnInfoChanged(info)) @RequiresApi(30) - override fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) = - push(SoftApCallbackParcel.OnCapabilityChanged(maxSupportedClients, supportedFeatures)) + override fun onCapabilityChanged(capability: Parcelable) = + push(SoftApCallbackParcel.OnCapabilityChanged(capability)) @RequiresApi(30) override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) = push(SoftApCallbackParcel.OnBlockedClientConnecting(client, blockedReason))