Support latest SACC features

This commit is contained in:
Mygod
2021-05-30 01:03:18 -04:00
parent ada55a1bf6
commit a6819a1def
7 changed files with 260 additions and 90 deletions

View File

@@ -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
}

View File

@@ -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<Int, Int> {
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())

View File

@@ -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<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), 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<BandOption.Channel>()
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<WifiApDialogFragment.Arg, WifiA
*/
val p2pMode: Boolean = false) : Parcelable
private sealed class BandOption {
open val band get() = SoftApConfigurationCompat.BAND_ANY
open val channel get() = 0
object BandAny : BandOption() {
private open class ChannelOption(val channel: Int = 0, val band: Int = 0) {
object Disabled : ChannelOption(-1) {
override fun toString() = app.getString(R.string.wifi_ap_choose_disabled)
}
object Auto : ChannelOption() {
override fun toString() = app.getString(R.string.wifi_ap_choose_auto)
}
object Band2GHz : BandOption() {
override val band get() = SoftApConfigurationCompat.BAND_2GHZ
override fun toString() = app.getString(R.string.wifi_ap_choose_2G)
}
object Band5GHz : BandOption() {
override val band get() = SoftApConfigurationCompat.BAND_5GHZ
override fun toString() = app.getString(R.string.wifi_ap_choose_5G)
}
@RequiresApi(30)
object Band6GHz : BandOption() {
override val band get() = SoftApConfigurationCompat.BAND_6GHZ
override fun toString() = app.getString(R.string.wifi_ap_choose_6G)
}
class Channel(override val band: Int, override val channel: Int) : BandOption() {
override fun toString() = "${SoftApConfigurationCompat.channelToFrequency(band, channel)} MHz ($channel)"
}
override fun toString() = "${SoftApConfigurationCompat.channelToFrequency(band, channel)} MHz ($channel)"
}
private lateinit var dialogView: DialogWifiApBinding
private lateinit var bandOptions: MutableList<BandOption>
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<WifiApDialogFragment.Arg, WifiA
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
}
if (Build.VERSION.SDK_INT >= 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<WifiApDialogFragment.Arg, WifiA
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
setMacRandomizationEnabled(dialogView.macRandomization.isChecked)
isBridgedModeOpportunisticShutdownEnabled = dialogView.bridgedModeOpportunisticShutdown.isChecked
isIeee80211axEnabled = dialogView.ieee80211ax.isChecked
isUserConfiguration = dialogView.userConfig.isChecked
}
}
@@ -163,27 +166,23 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
TetherTimeoutMonitor.defaultTimeout)
dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
} else dialogView.timeoutWrapper.isGone = true
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) dialogView.band.apply {
bandOptions = mutableListOf<BandOption>().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<ChannelOption>) {
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<WifiApDialogFragment.Arg, WifiA
dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment)
dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment)
}
if (arg.p2pMode && Build.VERSION.SDK_INT >= 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<ChannelOption>): 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<WifiApDialogFragment.Arg, WifiA
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
dialogView.timeout.setText(base.shutdownTimeoutMillis.let { if (it == 0L) "" else it.toString() })
if (Build.VERSION.SDK_INT >= 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<WifiApDialogFragment.Arg, WifiA
dialogView.clientUserControl.isChecked = base.isClientControlByUserEnabled
dialogView.blockedList.setText(base.blockedClientList.joinToString("\n"))
dialogView.allowedList.setText(base.allowedClientList.joinToString("\n"))
dialogView.macRandomization.isChecked =
base.macRandomizationSetting == SoftApConfigurationCompat.RANDOMIZATION_PERSISTENT
dialogView.bridgedModeOpportunisticShutdown.isChecked = base.isBridgedModeOpportunisticShutdownEnabled
dialogView.ieee80211ax.isChecked = base.isIeee80211axEnabled
dialogView.userConfig.isChecked = base.isUserConfiguration
}
override fun onStart() {
@@ -253,6 +272,29 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
}
}
dialogView.timeoutWrapper.error = timeoutError
val isBandValid = when {
arg.p2pMode || Build.VERSION.SDK_INT in 23 until 30 -> {
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<WifiApDialogFragment.Arg, WifiA
val canCopy = timeoutError == null && bssidValid && maxClientError == null && blockedListError == null &&
allowedListError == null
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
ssidLength in 1..32 && passwordValid && canCopy
ssidLength in 1..32 && passwordValid && isBandValid && canCopy
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
}
@@ -297,6 +339,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun afterTextChanged(editable: Editable) = validate()
override fun onItemSelected(parent: AdapterView<*>?, 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 -> {

View File

@@ -120,7 +120,7 @@
android:maxLength="19" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:id="@+id/band_wrapper"
android:id="@+id/band_wrapper_2G"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
@@ -129,14 +129,71 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_hotspot_ap_band_title" />
android:text="@string/wifi_ap_choose_2G" />
<Spinner
android:id="@+id/band"
android:id="@+id/band_2G"
style="@style/wifi_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/touch_target_min"
android:prompt="@string/wifi_hotspot_ap_band_title" />
android:prompt="@string/wifi_ap_choose_2G" />
</LinearLayout>
<LinearLayout
android:id="@+id/band_wrapper_5G"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_ap_choose_5G" />
<Spinner
android:id="@+id/band_5G"
style="@style/wifi_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/touch_target_min"
android:prompt="@string/wifi_ap_choose_5G" />
</LinearLayout>
<LinearLayout
android:id="@+id/band_wrapper_6G"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_ap_choose_6G" />
<Spinner
android:id="@+id/band_6G"
style="@style/wifi_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/touch_target_min"
android:prompt="@string/wifi_ap_choose_6G" />
</LinearLayout>
<LinearLayout
android:id="@+id/band_wrapper_60G"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/wifi_item_label"
android:text="@string/wifi_ap_choose_60G" />
<Spinner
android:id="@+id/band_60G"
style="@style/wifi_item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/touch_target_min"
android:prompt="@string/wifi_ap_choose_60G" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/bssid_wrapper"
@@ -216,6 +273,38 @@
style="@style/wifi_item_edit_content"
android:inputType="textMultiLine|textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:id="@+id/mac_randomization"
style="@style/wifi_item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:minHeight="@dimen/touch_target_min"
android:text="@string/wifi_mac_randomization" />
<Switch
android:id="@+id/bridged_mode_opportunistic_shutdown"
style="@style/wifi_item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:minHeight="@dimen/touch_target_min"
android:text="@string/wifi_bridged_mode_opportunistic_shutdown" />
<Switch
android:id="@+id/ieee_80211ax"
style="@style/wifi_item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:minHeight="@dimen/touch_target_min"
android:text="@string/wifi_ieee_80211ax" />
<Switch
android:id="@+id/user_config"
style="@style/wifi_item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
android:minHeight="@dimen/touch_target_min"
android:text="@string/wifi_user_config" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -172,16 +172,22 @@
<string name="wifi_hotspot_timeout">关闭延迟</string>
<string name="wifi_hotspot_timeout_default">默认延迟:%d 毫秒</string>
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"AP 频段"</string>
<string name="wifi_ap_choose_disabled">Disabled</string>
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"自动"</string>
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2.4 GHz 频段"</string>
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5.0 GHz 频段"</string>
<string name="wifi_ap_choose_6G">6.0 GHz 频段</string>
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5 GHz 频段"</string>
<string name="wifi_ap_choose_6G">6 GHz 频段</string>
<string name="wifi_ap_choose_60G">60 GHz 频段</string>
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
<string name="wifi_hidden_network" msgid="973162091800925000">"隐藏的网络"</string>
<string name="wifi_max_clients">允许连接设备数上限</string>
<string name="wifi_client_user_control">过滤可以连接的设备</string>
<string name="wifi_blocked_list">设备黑名单</string>
<string name="wifi_allowed_list">设备白名单</string>
<string name="wifi_mac_randomization">随机生成 MAC 地址</string>
<string name="wifi_bridged_mode_opportunistic_shutdown">启用桥接模式伺机关闭</string>
<string name="wifi_ieee_80211ax">启用 Wi\u2011Fi 6</string>
<string name="wifi_user_config">用户提供配置</string>
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->

View File

@@ -176,16 +176,20 @@
<string name="wifi_hotspot_timeout">關閉延遲時間</string>
<string name="wifi_hotspot_timeout_default">默認延遲:%d 毫秒</string>
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">AP 頻帶</string>
<string name="wifi_ap_choose_disabled">Disabled</string>
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">自動</string>
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">2.4 GHz 頻帶</string>
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">5.0 GHz 頻帶</string>
<string name="wifi_ap_choose_6G">6.0 GHz 頻帶</string>
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">5 GHz 頻帶</string>
<string name="wifi_ap_choose_6G">6 GHz 頻帶</string>
<string name="wifi_ap_choose_60G">60 GHz 頻帶</string>
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
<string name="wifi_hidden_network" msgid="973162091800925000">"隱藏的網路"</string>
<string name="wifi_max_clients">允許的連接裝置數量</string>
<string name="wifi_client_user_control">過濾可以連接的裝置</string>
<string name="wifi_blocked_list">裝置黑名單</string>
<string name="wifi_allowed_list">裝置白名單</string>
<string name="wifi_mac_randomization">隨機化 MAC 位址</string>
<string name="wifi_ieee_80211ax">啟用 Wi\u2011Fi 6</string>
<string name="wifi_save" msgid="3331121567988522826">儲存</string>
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->

View File

@@ -194,16 +194,22 @@
<string name="wifi_hotspot_timeout">Inactive timeout</string>
<string name="wifi_hotspot_timeout_default">Default timeout: %dms</string>
<string name="wifi_hotspot_ap_band_title">AP Band</string>
<string name="wifi_ap_choose_disabled">Disabled</string>
<string name="wifi_ap_choose_auto">Auto</string>
<string name="wifi_ap_choose_2G">2.4 GHz Band</string>
<string name="wifi_ap_choose_5G">5.0 GHz Band</string>
<string name="wifi_ap_choose_6G">6.0 GHz Band</string>
<string name="wifi_ap_choose_5G">5 GHz Band</string>
<string name="wifi_ap_choose_6G">6 GHz Band</string>
<string name="wifi_ap_choose_60G">60 GHz Band</string>
<string name="wifi_advanced_mac_address_title">MAC address</string>
<string name="wifi_hidden_network">Hidden network</string>
<string name="wifi_max_clients">Maximum number of clients</string>
<string name="wifi_client_user_control">Control which client can use hotspot</string>
<string name="wifi_blocked_list">Blocked list of clients</string>
<string name="wifi_allowed_list">Allowed list of clients</string>
<string name="wifi_mac_randomization">Use randomized MAC</string>
<string name="wifi_bridged_mode_opportunistic_shutdown">Enable bridged mode opportunistic shutdown</string>
<string name="wifi_ieee_80211ax">Enable Wi\u2011Fi 6</string>
<string name="wifi_user_config">User Supplied Configuration</string>
<string name="wifi_save">Save</string>
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->