From 50de5a269c1e43ad35af5a994389fdd62a851287 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 26 May 2021 20:56:44 -0400 Subject: [PATCH] Support new fields in SoftApInfo --- README.md | 5 ++ .../mygod/vpnhotspot/manage/TetherManager.kt | 59 ++++++++++++------- .../mygod/vpnhotspot/net/wifi/SoftApInfo.kt | 35 +++++++++-- .../mygod/vpnhotspot/util/UnblockCentral.kt | 32 ++++++++++ .../java/be/mygod/vpnhotspot/util/Utils.kt | 9 +++ mobile/src/main/res/values-zh-rCN/strings.xml | 4 ++ mobile/src/main/res/values/strings.xml | 4 ++ 7 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt diff --git a/README.md b/README.md index d51975fd..56e8fcaf 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Landroid/net/ConnectivityModuleConnector;->IN_PROCESS_SUFFIX:Ljava/lang/String;` * (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfaceRegexpsChanged(Landroid/net/TetheringManager$TetheringInterfaceRegexps;)V,blocked` * (since API 30) `Landroid/net/TetheringManager;->TETHERING_WIGIG:I,blocked` +* (since API 31) `Landroid/net/wifi/SoftApInfo;->getApInstanceIdentifier()Ljava/lang/String;,blocked` * (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->FT_PSK:I,lo-prio,max-target-o` * (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA_PSK_SHA256:I,blocked` * (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_2GHZ:I,lo-prio,max-target-o` @@ -176,6 +177,7 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Lcom/android/server/wifi/WifiContext;->ACTION_RESOURCES_APK:Ljava/lang/String;` * (since API 29) `Lcom/android/server/wifi/p2p/WifiP2pServiceImpl;->ANONYMIZED_DEVICE_ADDRESS:Ljava/lang/String;` * (since API 30) `Lcom/android/server/SystemServer;->TETHERING_CONNECTOR_CLASS:Ljava/lang/String;` +* (since API 31) `Ldalvik/system/VMDebug;->allowHiddenApiReflectionFrom(Ljava/lang/Class;)V,unsupported` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->(Ljava/lang/Class;I)V,unsupported` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o` * (prior to API 29) `Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,max-target-p` @@ -256,8 +258,11 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Landroid/net/wifi/SoftApConfiguration;->isClientControlByUserEnabled()Z,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_*:I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_INVALID:I,sdk,system-api,test-api` +* (since API 31) `Landroid/net/wifi/SoftApInfo;->getAutoShutdownTimeoutMillis()J,sdk,system-api,test-api` +* (since API 31) `Landroid/net/wifi/SoftApInfo;->getBssid()Landroid/net/MacAddress;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/SoftApInfo;->getBandwidth()I,system-api,whitelist` * (since API 30) `Landroid/net/wifi/SoftApInfo;->getFrequency()I,system-api,whitelist` +* (since API 31) `Landroid/net/wifi/SoftApInfo;->getWifiStandard()I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiClient;->getMacAddress()Landroid/net/MacAddress;,sdk,system-api,test-api` * (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA2_PSK:I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onBlockedClientConnecting(Landroid/net/wifi/WifiClient;I)V,sdk,system-api,test-api` 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 b25cd043..84d3d425 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -10,6 +10,8 @@ import android.net.MacAddress import android.os.Build import android.os.Parcelable import android.provider.Settings +import android.text.SpannableStringBuilder +import android.text.format.DateUtils import android.view.View import android.widget.Toast import androidx.annotation.RequiresApi @@ -29,6 +31,9 @@ import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat import be.mygod.vpnhotspot.net.wifi.SoftApInfo import be.mygod.vpnhotspot.net.wifi.WifiApManager import be.mygod.vpnhotspot.root.WifiApCommands +import be.mygod.vpnhotspot.util.format +import be.mygod.vpnhotspot.util.joinToSpanned +import be.mygod.vpnhotspot.util.makeMacSpan import be.mygod.vpnhotspot.util.readableMessage import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.coroutines.Dispatchers @@ -188,28 +193,40 @@ 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 - override val text get() = listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) }, baseError, - info.run { - if (isEmpty()) null else joinToString("\n") @TargetApi(30) { - val info = SoftApInfo(it) - val frequency = info.frequency - parent.getString(R.string.tethering_manage_wifi_info, frequency, - SoftApConfigurationCompat.frequencyToChannel(frequency), - SoftApInfo.channelWidthLookup(info.bandwidth, true)) - } - }, - capability?.let { (maxSupportedClients, supportedFeatures) -> - app.resources.getQuantityString(R.plurals.tethering_manage_wifi_capabilities, maxSupportedClients, - numClients ?: "?", maxSupportedClients, sequence { - var features = supportedFeatures - if (features == 0L) yield(parent.getString(R.string.tethering_manage_wifi_no_features)) - else while (features != 0L) { - val bit = features.takeLowestOneBit() - yield(WifiApManager.featureLookup(bit, true)) - features = features and bit.inv() + 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 -> + val info = SoftApInfo(parcel) + val frequency = info.frequency + val channel = SoftApConfigurationCompat.frequencyToChannel(frequency) + val bandwidth = SoftApInfo.channelWidthLookup(info.bandwidth, true) + if (BuildCompat.isAtLeastS()) { + var bssid = makeMacSpan(info.bssid.toString()) + info.apInstanceIdentifier?.let { // take the fast route if possible + bssid = if (bssid is String) "$bssid%$it" else SpannableStringBuilder(bssid).append("%$it") } - }.joinToString()) - }).joinToString("\n") + val timeout = info.autoShutdownTimeoutMillis + parent.getText(if (timeout == 0L) { + R.string.tethering_manage_wifi_info_timeout_disabled + } else R.string.tethering_manage_wifi_info_timeout_enabled).format(locale, + frequency, channel, bandwidth, bssid, info.wifiStandard, + // http://unicode.org/cldr/trac/ticket/3407 + DateUtils.formatElapsedTime(timeout / 1000)) + } 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") + } override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this) override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt index c5de91de..1b6e0d73 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt @@ -1,20 +1,47 @@ package be.mygod.vpnhotspot.net.wifi +import android.annotation.TargetApi +import android.net.MacAddress import android.os.Parcelable import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.util.ConstantLookup +import be.mygod.vpnhotspot.util.UnblockCentral +import timber.log.Timber @JvmInline @RequiresApi(30) value class SoftApInfo(val inner: Parcelable) { companion object { - private val classSoftApInfo by lazy { Class.forName("android.net.wifi.SoftApInfo") } - private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") } - private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") } + private 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) + private val getBssid by lazy { clazz.getDeclaredMethod("getBssid") } + @get:RequiresApi(31) + private val getWifiStandard by lazy { clazz.getDeclaredMethod("getWifiStandard") } + @get:RequiresApi(31) + private val getApInstanceIdentifier by lazy @TargetApi(31) { + UnblockCentral.SoftApInfo_getApInstanceIdentifier(clazz) + } + @get:RequiresApi(31) + private val getAutoShutdownTimeoutMillis by lazy { clazz.getDeclaredMethod("getAutoShutdownTimeoutMillis") } - val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { classSoftApInfo } + val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { clazz } } val frequency get() = getFrequency.invoke(inner) as Int val bandwidth get() = getBandwidth.invoke(inner) as Int + @get:RequiresApi(31) + val bssid get() = getBssid.invoke(inner) as MacAddress + @get:RequiresApi(31) + val wifiStandard get() = getWifiStandard.invoke(inner) as Int + @get:RequiresApi(31) + val apInstanceIdentifier get() = try { + getApInstanceIdentifier.invoke(inner) as? String + } catch (e: ReflectiveOperationException) { + Timber.w(e) + null + } + @get:RequiresApi(31) + val autoShutdownTimeoutMillis get() = getAutoShutdownTimeoutMillis.invoke(inner) as Long } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt new file mode 100644 index 00000000..2eceab4e --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt @@ -0,0 +1,32 @@ +package be.mygod.vpnhotspot.util + +import android.annotation.SuppressLint +import androidx.annotation.RequiresApi +import timber.log.Timber + +/** + * The central object for accessing all the useful blocked APIs. Thanks Google! + */ +@SuppressLint("DiscouragedPrivateApi") +@Suppress("FunctionName") +object UnblockCentral { + /** + * Retrieve this property before doing dangerous shit. + */ + @get:RequiresApi(28) + private val init by lazy { + try { + Class.forName("dalvik.system.VMDebug").getDeclaredMethod("allowHiddenApiReflectionFrom", Class::class.java) + .invoke(null, UnblockCentral::class.java) + true + } catch (e: ReflectiveOperationException) { + Timber.w(e) + false + } + } + + @RequiresApi(31) + fun SoftApInfo_getApInstanceIdentifier(clazz: Class<*>) = init.let { + clazz.getDeclaredMethod("getApInstanceIdentifier") + } +} 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 998b464e..49b0ac70 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -126,6 +126,15 @@ fun CharSequence.format(locale: Locale, vararg args: Any) = SpannableStringBuild } } +fun Iterable.joinToSpanned(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", + limit: Int = -1, truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null) = + joinTo(SpannableStringBuilder(), separator, prefix, postfix, limit, truncated, transform) +fun Sequence.joinToSpanned(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", + limit: Int = -1, truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null) = + joinTo(SpannableStringBuilder(), separator, prefix, postfix, limit, truncated, transform) + fun makeIpSpan(ip: InetAddress) = ip.hostAddress.let { // exclude all bogon IP addresses supported by Android APIs if (!app.hasTouch || ip.isMulticastAddress || ip.isAnyLocalAddress || ip.isLoopbackAddress || diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 37095766..52011a0f 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -59,6 +59,10 @@ USB 网络共享 (NCM) WiGig 热点 %1$d MHz, 频道 %2$d, 频宽 %3$s + %4$s: Wi\u2011Fi %5$d, %1$d MHz, 频道 %2$d, 频宽 %3$s, + 关闭延迟 %6$s + %4$s: Wi\u2011Fi %5$d, %1$d MHz, 频道 %2$d, 频宽 %3$s, + 不自动关闭 已连接 %1$s/%2$d 个设备\n支持功能:%3$s diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index f3ce86b8..4a6154e9 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -70,6 +70,10 @@ USB tethering (NCM) WiGig hotspot %1$d MHz, channel %2$d, width %3$s + %4$s: Wi\u2011Fi %5$d, %1$d MHz, channel %2$d, + width %3$s, idle timeout in %6$s + %4$s: Wi\u2011Fi %5$d, %1$d MHz, channel %2$d, + width %3$s, idle timeout disabled %1$s/%2$d client connected\nSupported features: %3$s %1$s/%2$d clients connected\nSupported features: %3$s