From a6819a1def8e29664204cda1ea99dce22b3ea4b7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 30 May 2021 01:03:18 -0400 Subject: [PATCH] Support latest SACC features --- .../vpnhotspot/manage/RepeaterManager.kt | 6 +- .../net/wifi/SoftApConfigurationCompat.kt | 52 ++++-- .../net/wifi/WifiApDialogFragment.kt | 167 +++++++++++------- mobile/src/main/res/layout/dialog_wifi_ap.xml | 97 +++++++++- mobile/src/main/res/values-zh-rCN/strings.xml | 10 +- mobile/src/main/res/values-zh-rTW/strings.xml | 8 +- mobile/src/main/res/values/strings.xml | 10 +- 7 files changed, 260 insertions(+), 90 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt index b3aabd03..137fd2b1 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -231,7 +231,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic return null } private suspend fun updateConfiguration(config: SoftApConfigurationCompat) { - config.requireSingleBand() + val (band, channel) = config.requireSingleBand() if (RepeaterService.safeMode) { RepeaterService.networkName = config.ssid RepeaterService.deviceAddress = config.bssid @@ -248,8 +248,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic } holder.config = null } - RepeaterService.operatingBand = config.band - RepeaterService.operatingChannel = config.channel + RepeaterService.operatingBand = band + RepeaterService.operatingChannel = channel RepeaterService.isAutoShutdownEnabled = config.isAutoShutdownEnabled RepeaterService.shutdownTimeoutMillis = config.shutdownTimeoutMillis } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt index 6a596c14..98b7f145 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt @@ -9,6 +9,7 @@ import android.os.Parcelable import android.util.SparseIntArray import androidx.annotation.RequiresApi import androidx.core.os.BuildCompat +import androidx.core.util.keyIterator import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor @@ -24,6 +25,11 @@ data class SoftApConfigurationCompat( var bssidAddr: Long? = null, var passphrase: String? = null, var isHiddenSsid: Boolean = false, + /** + * To read legacy band/channel pair, use [requireSingleBand]. For easy access, see [getChannel]. + * + * You should probably not set or modify this field directly. Use [optimizeChannels] or [setChannel]. + */ @TargetApi(23) var channels: SparseIntArray = SparseIntArray(1).apply { put(BAND_2GHZ, 0) }, var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN, @@ -333,25 +339,38 @@ data class SoftApConfigurationCompat( set(value) { bssidAddr = value?.addr } - @get:Deprecated("Use channels", ReplaceWith("channels.keyAt(0)")) - @get:TargetApi(23) - val band get() = channels.keyAt(0) - @get:Deprecated("Use channels", ReplaceWith("channels.valueAt(0)")) - @get:TargetApi(23) - val channel get() = channels.valueAt(0) + + /** + * Only single band/channel can be supplied on API 23-30 + */ + fun requireSingleBand(): Pair { + require(channels.size() == 1) { "Unsupported number of bands configured" } + return channels.keyAt(0) to channels.valueAt(0) + } + fun getChannel(band: Int): Int { + var result = -1 + for (b in channels.keyIterator()) if (band and b == band) { + require(result == -1) { "Duplicate band found" } + result = channels[b] + } + return result + } fun setChannel(channel: Int, band: Int = BAND_ANY) { channels = SparseIntArray(1).apply { put(band, channel) } } + fun optimizeChannels(channels: SparseIntArray = this.channels) { + this.channels = SparseIntArray(channels.size()).apply { + var setBand = 0 + for (band in channels.keyIterator()) if (channels[band] == 0) setBand = setBand or band + if (setBand != 0) put(setBand, 0) // merge all bands into one + for (band in channels.keyIterator()) if (band and setBand == 0) put(band, channels[band]) + } + } fun setMacRandomizationEnabled(enabled: Boolean) { macRandomizationSetting = if (enabled) RANDOMIZATION_PERSISTENT else RANDOMIZATION_NONE } - /** - * Only single band/channel can be supplied on API 23-30 - */ - fun requireSingleBand() = require(channels.size() == 1) { "Unsupported number of bands configured" } - /** * Based on: * https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88 @@ -362,7 +381,7 @@ data class SoftApConfigurationCompat( @Deprecated("Class deprecated in framework, use toPlatform().toWifiConfiguration()") @Suppress("DEPRECATION") fun toWifiConfiguration(): android.net.wifi.WifiConfiguration { - requireSingleBand() + val (band, channel) = requireSingleBand() val wc = underlying as? android.net.wifi.WifiConfiguration val result = if (wc == null) android.net.wifi.WifiConfiguration() else android.net.wifi.WifiConfiguration(wc) val original = wc?.toCompat() @@ -373,8 +392,10 @@ data class SoftApConfigurationCompat( apBand.setInt(result, when (band) { BAND_2GHZ -> 0 BAND_5GHZ -> 1 - BAND_2GHZ or BAND_5GHZ, BAND_ANY -> -1 - else -> throw IllegalArgumentException("Convert fail, unsupported band setting :$band") + else -> (-1).also { + require(Build.VERSION.SDK_INT >= 28) { "A band must be specified on this platform" } + require(isLegacyEitherBand(band)) { "Convert fail, unsupported band setting :$band" } + } }) apChannel.setInt(result, channel) } else require(isLegacyEitherBand(band)) { "Specifying band is unsupported on this platform" } @@ -405,8 +426,7 @@ data class SoftApConfigurationCompat( setPassphrase(builder, if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) null else passphrase, securityType) if (BuildCompat.isAtLeastS()) setChannels(builder, channels) else { - requireSingleBand() - @Suppress("DEPRECATION") + val (band, channel) = requireSingleBand() if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band) } setBssid(builder, bssid?.toPlatform()) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt index becf1423..5dd1b94a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt @@ -10,13 +10,16 @@ import android.os.Parcelable import android.text.Editable import android.text.TextWatcher import android.util.Base64 +import android.util.SparseIntArray import android.view.MenuItem import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter +import android.widget.Spinner import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar +import androidx.core.os.BuildCompat import androidx.core.view.isGone import be.mygod.librootkotlinx.toByteArray import be.mygod.librootkotlinx.toParcelable @@ -32,6 +35,7 @@ import be.mygod.vpnhotspot.util.readableMessage import be.mygod.vpnhotspot.util.showAllowingStateLoss import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.parcelize.Parcelize +import timber.log.Timber /** * Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java @@ -40,27 +44,30 @@ import kotlinx.parcelize.Parcelize * Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea */ class WifiApDialogFragment : AlertDialogFragment(), TextWatcher, - Toolbar.OnMenuItemClickListener { + Toolbar.OnMenuItemClickListener, AdapterView.OnItemSelectedListener { companion object { private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP private val nonMacChars = "[^0-9a-fA-F:]+".toRegex() - private val channels by lazy { - val list = ArrayList() - for (chan in 1..14) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_2GHZ, chan)) - for (chan in 1..196) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_5GHZ, chan)) - if (Build.VERSION.SDK_INT >= 30) { - for (chan in 1..253) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_6GHZ, chan)) - } - list + private val baseOptions by lazy { listOf(ChannelOption.Disabled, ChannelOption.Auto) } + private val channels2G by lazy { + baseOptions + (1..14).map { ChannelOption(it, SoftApConfigurationCompat.BAND_2GHZ) } + } + private val channels5G by lazy { + baseOptions + (1..196).map { ChannelOption(it, SoftApConfigurationCompat.BAND_5GHZ) } + } + @get:RequiresApi(30) + private val channels6G by lazy { + baseOptions + (1..233).map { ChannelOption(it, SoftApConfigurationCompat.BAND_6GHZ) } + } + @get:RequiresApi(31) + private val channels60G by lazy { + baseOptions + (1..6).map { ChannelOption(it, SoftApConfigurationCompat.BAND_60GHZ) } } /** * Source: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/c2fc6a1/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java#1396 */ private val p2pChannels by lazy { - (1..165).map { - val band = if (it <= 14) SoftApConfigurationCompat.BAND_2GHZ else SoftApConfigurationCompat.BAND_5GHZ - BandOption.Channel(band, it) - } + baseOptions + (15..165).map { ChannelOption(it, SoftApConfigurationCompat.BAND_5GHZ) } } } @@ -73,35 +80,20 @@ class WifiApDialogFragment : AlertDialogFragment private lateinit var base: SoftApConfigurationCompat private var started = false + private val currentChannels5G get() = if (arg.p2pMode && !RepeaterService.safeMode) p2pChannels else channels5G override val ret get() = Arg(generateConfig()) private fun generateConfig(full: Boolean = true) = base.copy( @@ -117,8 +109,15 @@ class WifiApDialogFragment : AlertDialogFragment= 23 || arg.p2pMode) { - val bandOption = dialogView.band.selectedItem as BandOption - setChannel(bandOption.channel, bandOption.band) + val channels = SparseIntArray(4) + for ((band, spinner) in arrayOf(SoftApConfigurationCompat.BAND_2GHZ to dialogView.band2G, + SoftApConfigurationCompat.BAND_5GHZ to dialogView.band5G, + SoftApConfigurationCompat.BAND_6GHZ to dialogView.band6G, + SoftApConfigurationCompat.BAND_60GHZ to dialogView.band60G)) { + val channel = (spinner.selectedItem as ChannelOption?)?.channel + if (channel != null && channel >= 0) channels.put(band, channel) + } + optimizeChannels(channels) } bssid = if (dialogView.bssid.length() != 0) { MacAddressCompat.fromString(dialogView.bssid.text.toString()) @@ -131,6 +130,10 @@ class WifiApDialogFragment : AlertDialogFragment= 23 || arg.p2pMode) dialogView.band.apply { - bandOptions = mutableListOf().apply { - if (arg.p2pMode) { - add(BandOption.BandAny) - if (RepeaterService.safeMode) { - add(BandOption.Band2GHz) - add(BandOption.Band5GHz) - addAll(channels) - } else addAll(p2pChannels) - } else { - if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny) - add(BandOption.Band2GHz) - add(BandOption.Band5GHz) - if (Build.VERSION.SDK_INT >= 30) add(BandOption.Band6GHz) - addAll(channels) - } - } - adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, bandOptions).apply { + fun Spinner.configure(options: List) { + adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, options).apply { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) } - } else dialogView.bandWrapper.isGone = true + onItemSelectedListener = this@WifiApDialogFragment + } + if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) { + dialogView.band2G.configure(channels2G) + dialogView.band5G.configure(currentChannels5G) + } else { + dialogView.bandWrapper2G.isGone = true + dialogView.bandWrapper5G.isGone = true + } + if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) dialogView.band6G.configure(channels6G) + else dialogView.bandWrapper6G.isGone = true + if (BuildCompat.isAtLeastS() && !arg.p2pMode) dialogView.band60G.configure(channels60G) + else dialogView.bandWrapper60G.isGone = true dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment) if (arg.p2pMode) dialogView.hiddenSsid.isGone = true if (arg.p2pMode || Build.VERSION.SDK_INT < 30) { @@ -196,10 +195,25 @@ class WifiApDialogFragment : AlertDialogFragment= 29) dialogView.macRandomization.isEnabled = false + else if (arg.p2pMode || !BuildCompat.isAtLeastS()) dialogView.macRandomization.isGone = true + if (arg.p2pMode || !BuildCompat.isAtLeastS()) { + dialogView.bridgedModeOpportunisticShutdown.isGone = true + dialogView.ieee80211ax.isGone = true + dialogView.userConfig.isGone = true + } base = arg.configuration populateFromConfiguration() } + private fun locate(band: Int, channels: List): Int { + val channel = base.getChannel(band) + val selection = channels.indexOfFirst { it.channel == channel } + return if (selection == -1) { + Timber.w(Exception("Unable to locate $band, $channel, ${arg.p2pMode && !RepeaterService.safeMode}")) + 0 + } else selection + } private fun populateFromConfiguration() { dialogView.ssid.setText(base.ssid) if (!arg.p2pMode) dialogView.security.setSelection(base.securityType) @@ -207,10 +221,10 @@ class WifiApDialogFragment : AlertDialogFragment= 23 || arg.p2pMode) { - val selection = if (base.channel != 0) { - bandOptions.indexOfFirst { it.channel == base.channel } - } else bandOptions.indexOfFirst { it.band == base.band } - dialogView.band.setSelection(if (selection == -1) 0 else selection) + dialogView.band2G.setSelection(locate(SoftApConfigurationCompat.BAND_2GHZ, channels2G)) + dialogView.band5G.setSelection(locate(SoftApConfigurationCompat.BAND_5GHZ, currentChannels5G)) + dialogView.band6G.setSelection(locate(SoftApConfigurationCompat.BAND_6GHZ, channels6G)) + dialogView.band60G.setSelection(locate(SoftApConfigurationCompat.BAND_60GHZ, channels60G)) } dialogView.bssid.setText(base.bssid?.toString()) dialogView.hiddenSsid.isChecked = base.isHiddenSsid @@ -218,6 +232,11 @@ class WifiApDialogFragment : AlertDialogFragment { + val option5G = dialogView.band5G.selectedItem + when (dialogView.band2G.selectedItem) { + is ChannelOption.Disabled -> option5G !is ChannelOption.Disabled && + (!arg.p2pMode || RepeaterService.safeMode || option5G !is ChannelOption.Auto) + is ChannelOption.Auto -> + (arg.p2pMode || Build.VERSION.SDK_INT >= 28) && option5G is ChannelOption.Auto || + (!arg.p2pMode || RepeaterService.safeMode) && option5G is ChannelOption.Disabled + else -> option5G is ChannelOption.Disabled + } + } + Build.VERSION.SDK_INT == 30 -> { + var expected = 1 + var set = 0 + for (s in arrayOf(dialogView.band2G, dialogView.band5G, dialogView.band6G)) when (s.selectedItem) { + is ChannelOption.Auto -> expected = 0 + !is ChannelOption.Disabled -> ++set + } + set == expected + } + else -> true + } dialogView.bssidWrapper.error = null val bssidValid = dialogView.bssid.length() == 0 || try { MacAddressCompat.fromString(dialogView.bssid.text.toString()) @@ -289,7 +331,7 @@ class WifiApDialogFragment : AlertDialogFragment?, view: View?, position: Int, id: Long) = validate() + override fun onNothingSelected(parent: AdapterView<*>?) = error("unreachable") + override fun onMenuItemClick(item: MenuItem?): Boolean { return when (item?.itemId) { android.R.id.copy -> { diff --git a/mobile/src/main/res/layout/dialog_wifi_ap.xml b/mobile/src/main/res/layout/dialog_wifi_ap.xml index 9d44a04a..ed9e4478 100644 --- a/mobile/src/main/res/layout/dialog_wifi_ap.xml +++ b/mobile/src/main/res/layout/dialog_wifi_ap.xml @@ -120,7 +120,7 @@ android:maxLength="19" /> + android:text="@string/wifi_ap_choose_2G" /> + android:prompt="@string/wifi_ap_choose_2G" /> + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index f6586d35..2ed6c801 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -172,16 +172,22 @@ 关闭延迟 默认延迟:%d 毫秒 "AP 频段" + Disabled "自动" "2.4 GHz 频段" - "5.0 GHz 频段" - 6.0 GHz 频段 + "5 GHz 频段" + 6 GHz 频段 + 60 GHz 频段 "MAC 地址" "隐藏的网络" 允许连接设备数上限 过滤可以连接的设备 设备黑名单 设备白名单 + 随机生成 MAC 地址 + 启用桥接模式伺机关闭 + 启用 Wi\u2011Fi 6 + 用户提供配置 "保存" diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index e7a0b381..ac37f4b9 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -176,16 +176,20 @@ 關閉延遲時間 默認延遲:%d 毫秒 AP 頻帶 + Disabled 自動 2.4 GHz 頻帶 - 5.0 GHz 頻帶 - 6.0 GHz 頻帶 + 5 GHz 頻帶 + 6 GHz 頻帶 + 60 GHz 頻帶 "MAC 地址" "隱藏的網路" 允許的連接裝置數量 過濾可以連接的裝置 裝置黑名單 裝置白名單 + 隨機化 MAC 位址 + 啟用 Wi\u2011Fi 6 儲存 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 2b6b9933..95eebc0c 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -194,16 +194,22 @@ Inactive timeout Default timeout: %dms AP Band + Disabled Auto 2.4 GHz Band - 5.0 GHz Band - 6.0 GHz Band + 5 GHz Band + 6 GHz Band + 60 GHz Band MAC address Hidden network Maximum number of clients Control which client can use hotspot Blocked list of clients Allowed list of clients + Use randomized MAC + Enable bridged mode opportunistic shutdown + Enable Wi\u2011Fi 6 + User Supplied Configuration Save