Support hex-encoded SSID
This commit is contained in:
@@ -261,8 +261,9 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
|
|||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setMaxNumberOfClients(I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setMaxNumberOfClients(I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setPassphrase(Ljava/lang/String;I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setPassphrase(Ljava/lang/String;I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setShutdownTimeoutMillis(J)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setShutdownTimeoutMillis(J)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setSsid(Ljava/lang/String;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
* (since API 30, prior to API 33) `Landroid/net/wifi/SoftApConfiguration$Builder;->setSsid(Ljava/lang/String;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 33) `Landroid/net/wifi/SoftApConfiguration$Builder;->setVendorElements(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
* (since API 33) `Landroid/net/wifi/SoftApConfiguration$Builder;->setVendorElements(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
|
* (since API 33) `Landroid/net/wifi/SoftApConfiguration$Builder;->setWifiSsid(Landroid/net/wifi/WifiSsid;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_2GHZ:I,sdk,system-api,test-api`
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_2GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_5GHZ:I,sdk,system-api,test-api`
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_5GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->BAND_60GHZ:I,sdk,system-api,test-api`
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->BAND_60GHZ:I,sdk,system-api,test-api`
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupI
|
|||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setVendorElements
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setVendorElements
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
|
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiSsidCompat
|
||||||
import be.mygod.vpnhotspot.root.RepeaterCommands
|
import be.mygod.vpnhotspot.root.RepeaterCommands
|
||||||
import be.mygod.vpnhotspot.root.RootManager
|
import be.mygod.vpnhotspot.root.RootManager
|
||||||
import be.mygod.vpnhotspot.util.*
|
import be.mygod.vpnhotspot.util.*
|
||||||
@@ -53,6 +54,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
const val KEY_SAFE_MODE = "service.repeater.safeMode"
|
const val KEY_SAFE_MODE = "service.repeater.safeMode"
|
||||||
|
|
||||||
private const val KEY_NETWORK_NAME = "service.repeater.networkName"
|
private const val KEY_NETWORK_NAME = "service.repeater.networkName"
|
||||||
|
private const val KEY_NETWORK_NAME_HEX = "service.repeater.networkNameHex"
|
||||||
private const val KEY_PASSPHRASE = "service.repeater.passphrase"
|
private const val KEY_PASSPHRASE = "service.repeater.passphrase"
|
||||||
private const val KEY_OPERATING_BAND = "service.repeater.band.v4"
|
private const val KEY_OPERATING_BAND = "service.repeater.band.v4"
|
||||||
private const val KEY_OPERATING_CHANNEL = "service.repeater.oc.v3"
|
private const val KEY_OPERATING_CHANNEL = "service.repeater.oc.v3"
|
||||||
@@ -76,9 +78,16 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
@get:RequiresApi(29)
|
@get:RequiresApi(29)
|
||||||
private val mNetworkName by lazy @TargetApi(29) { UnblockCentral.WifiP2pConfig_Builder_mNetworkName }
|
private val mNetworkName by lazy @TargetApi(29) { UnblockCentral.WifiP2pConfig_Builder_mNetworkName }
|
||||||
|
|
||||||
var networkName: String?
|
var networkName: WifiSsidCompat?
|
||||||
get() = app.pref.getString(KEY_NETWORK_NAME, null)
|
get() = app.pref.getString(KEY_NETWORK_NAME, null).let { legacy ->
|
||||||
set(value) = app.pref.edit { putString(KEY_NETWORK_NAME, value) }
|
if (legacy != null) WifiSsidCompat.fromUtf8Text(legacy).also {
|
||||||
|
app.pref.edit {
|
||||||
|
putString(KEY_NETWORK_NAME_HEX, it!!.hex)
|
||||||
|
remove(KEY_NETWORK_NAME)
|
||||||
|
}
|
||||||
|
} else WifiSsidCompat.fromHex(app.pref.getString(KEY_NETWORK_NAME_HEX, null))
|
||||||
|
}
|
||||||
|
set(value) = app.pref.edit { putString(KEY_NETWORK_NAME_HEX, value?.hex) }
|
||||||
var passphrase: String?
|
var passphrase: String?
|
||||||
get() = app.pref.getString(KEY_PASSPHRASE, null)
|
get() = app.pref.getString(KEY_PASSPHRASE, null)
|
||||||
set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) }
|
set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) }
|
||||||
@@ -447,10 +456,10 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
setOperatingChannel()
|
setOperatingChannel()
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 33) setVendorElements()
|
if (Build.VERSION.SDK_INT >= 33) setVendorElements()
|
||||||
val networkName = networkName
|
val networkName = networkName?.toString()
|
||||||
val passphrase = passphrase
|
val passphrase = passphrase
|
||||||
@SuppressLint("MissingPermission") // missing permission will simply leading to returning ERROR
|
@SuppressLint("MissingPermission") // missing permission will simply leading to returning ERROR
|
||||||
if (!safeMode || networkName.isNullOrEmpty() || passphrase.isNullOrEmpty()) {
|
if (!safeMode || networkName == null || passphrase.isNullOrEmpty()) {
|
||||||
persistNextGroup = true
|
persistNextGroup = true
|
||||||
p2pManager.createGroup(channel, listener)
|
p2pManager.createGroup(channel, listener)
|
||||||
} else @TargetApi(29) {
|
} else @TargetApi(29) {
|
||||||
@@ -510,7 +519,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
}
|
}
|
||||||
binder.group = group
|
binder.group = group
|
||||||
if (persistNextGroup) {
|
if (persistNextGroup) {
|
||||||
networkName = group.networkName
|
networkName = WifiSsidCompat.fromUtf8Text(group.networkName)
|
||||||
passphrase = group.passphrase
|
passphrase = group.passphrase
|
||||||
persistNextGroup = false
|
persistNextGroup = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class LocalOnlyHotspotTileService : IpNeighbourMonitoringTileService() {
|
|||||||
label = getText(R.string.tethering_temp_hotspot)
|
label = getText(R.string.tethering_temp_hotspot)
|
||||||
} else {
|
} else {
|
||||||
state = Tile.STATE_ACTIVE
|
state = Tile.STATE_ACTIVE
|
||||||
label = binder.configuration?.ssid ?: getText(R.string.tethering_temp_hotspot)
|
label = binder.configuration?.ssid?.toString() ?: getText(R.string.tethering_temp_hotspot)
|
||||||
subtitleDevices { it == iface }
|
subtitleDevices { it == iface }
|
||||||
}
|
}
|
||||||
updateTile()
|
updateTile()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
|
|||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
|
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiSsidCompat
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
@@ -215,7 +216,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
} else binder?.let { binder ->
|
} else binder?.let { binder ->
|
||||||
val group = binder.group ?: binder.fetchPersistentGroup().let { binder.group }
|
val group = binder.group ?: binder.fetchPersistentGroup().let { binder.group }
|
||||||
if (group != null) return SoftApConfigurationCompat(
|
if (group != null) return SoftApConfigurationCompat(
|
||||||
ssid = group.networkName,
|
ssid = WifiSsidCompat.fromUtf8Text(group.networkName),
|
||||||
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
||||||
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
||||||
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis,
|
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis,
|
||||||
@@ -253,8 +254,12 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
RepeaterService.passphrase = config.passphrase
|
RepeaterService.passphrase = config.passphrase
|
||||||
} else holder.config?.let { master ->
|
} else holder.config?.let { master ->
|
||||||
val binder = binder
|
val binder = binder
|
||||||
if (binder?.group?.networkName != config.ssid || master.psk != config.passphrase ||
|
val mayBeModified = master.psk != config.passphrase || master.bssid != config.bssid || config.ssid.run {
|
||||||
master.bssid != config.bssid) try {
|
if (this != null) decode().let {
|
||||||
|
it == null || binder?.group?.networkName != it
|
||||||
|
} else binder?.group?.networkName != null
|
||||||
|
}
|
||||||
|
if (mayBeModified) try {
|
||||||
withContext(Dispatchers.Default) { master.update(config.ssid!!, config.passphrase!!, config.bssid) }
|
withContext(Dispatchers.Default) { master.update(config.ssid!!, config.passphrase!!, config.bssid) }
|
||||||
(this.binder ?: binder)?.group = null
|
(this.binder ?: binder)?.group = null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -145,10 +145,9 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
|
|||||||
content.target.bssid?.let { MacAddress.fromString(it) }
|
content.target.bssid?.let { MacAddress.fromString(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun update(ssid: String, psk: String, bssid: MacAddress?) {
|
suspend fun update(ssid: WifiSsidCompat, psk: String, bssid: MacAddress?) {
|
||||||
val (lines, block, persistentMacLine, legacy) = content
|
val (lines, block, persistentMacLine, legacy) = content
|
||||||
block[block.ssidLine!!] = "\tssid=" + ssid.toByteArray()
|
block[block.ssidLine!!] = "\tssid=${ssid.hex}"
|
||||||
.joinToString("") { (it.toInt() and 255).toString(16).padStart(2, '0') }
|
|
||||||
block[block.pskLine!!] = "\tpsk=\"$psk\"" // no control chars or weird stuff
|
block[block.pskLine!!] = "\tpsk=\"$psk\"" // no control chars or weird stuff
|
||||||
if (bssid != null) {
|
if (bssid != null) {
|
||||||
persistentMacLine?.let { lines[it] = PERSISTENT_MAC + bssid }
|
persistentMacLine?.let { lines[it] = PERSISTENT_MAC + bssid }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.annotation.TargetApi
|
|||||||
import android.net.MacAddress
|
import android.net.MacAddress
|
||||||
import android.net.wifi.ScanResult
|
import android.net.wifi.ScanResult
|
||||||
import android.net.wifi.SoftApConfiguration
|
import android.net.wifi.SoftApConfiguration
|
||||||
|
import android.net.wifi.WifiSsid
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.SparseIntArray
|
import android.util.SparseIntArray
|
||||||
@@ -12,6 +13,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.requireSingleBand
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.requireSingleBand
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.setChannel
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.setChannel
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiSsidCompat.Companion.toCompat
|
||||||
import be.mygod.vpnhotspot.util.ConstantLookup
|
import be.mygod.vpnhotspot.util.ConstantLookup
|
||||||
import be.mygod.vpnhotspot.util.UnblockCentral
|
import be.mygod.vpnhotspot.util.UnblockCentral
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
@@ -20,7 +22,7 @@ import java.lang.reflect.InvocationTargetException
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class SoftApConfigurationCompat(
|
data class SoftApConfigurationCompat(
|
||||||
var ssid: String? = null,
|
var ssid: WifiSsidCompat? = null,
|
||||||
var bssid: MacAddress? = null,
|
var bssid: MacAddress? = null,
|
||||||
var passphrase: String? = null,
|
var passphrase: String? = null,
|
||||||
var isHiddenSsid: Boolean = false,
|
var isHiddenSsid: Boolean = false,
|
||||||
@@ -116,8 +118,6 @@ data class SoftApConfigurationCompat(
|
|||||||
"WPA3-OWE",
|
"WPA3-OWE",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val qrSanitizer = Regex("([\\\\\":;,])")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on:
|
* Based on:
|
||||||
* https://elixir.bootlin.com/linux/v5.12.8/source/net/wireless/util.c#L75
|
* https://elixir.bootlin.com/linux/v5.12.8/source/net/wireless/util.c#L75
|
||||||
@@ -344,11 +344,15 @@ data class SoftApConfigurationCompat(
|
|||||||
private val setVendorElements by lazy @TargetApi(33) {
|
private val setVendorElements by lazy @TargetApi(33) {
|
||||||
classBuilder.getDeclaredMethod("setVendorElements", java.util.List::class.java)
|
classBuilder.getDeclaredMethod("setVendorElements", java.util.List::class.java)
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(33)
|
||||||
|
private val setWifiSsid by lazy @TargetApi(33) {
|
||||||
|
classBuilder.getDeclaredMethod("setWifiSsid", WifiSsid::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated("Class deprecated in framework")
|
@Deprecated("Class deprecated in framework")
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun android.net.wifi.WifiConfiguration.toCompat() = SoftApConfigurationCompat(
|
fun android.net.wifi.WifiConfiguration.toCompat() = SoftApConfigurationCompat(
|
||||||
SSID,
|
WifiSsidCompat.fromUtf8Text(SSID),
|
||||||
BSSID?.let { MacAddress.fromString(it) },
|
BSSID?.let { MacAddress.fromString(it) },
|
||||||
preSharedKey,
|
preSharedKey,
|
||||||
hiddenSSID,
|
hiddenSSID,
|
||||||
@@ -388,7 +392,9 @@ data class SoftApConfigurationCompat(
|
|||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
|
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
|
||||||
ssid,
|
if (Build.VERSION.SDK_INT >= 33) wifiSsid?.toCompat() else @Suppress("DEPRECATION") {
|
||||||
|
WifiSsidCompat.fromUtf8Text(ssid)
|
||||||
|
},
|
||||||
bssid,
|
bssid,
|
||||||
passphrase,
|
passphrase,
|
||||||
isHiddenSsid,
|
isHiddenSsid,
|
||||||
@@ -483,7 +489,7 @@ data class SoftApConfigurationCompat(
|
|||||||
val wc = underlying as? android.net.wifi.WifiConfiguration
|
val wc = underlying as? android.net.wifi.WifiConfiguration
|
||||||
val result = if (wc == null) android.net.wifi.WifiConfiguration() else android.net.wifi.WifiConfiguration(wc)
|
val result = if (wc == null) android.net.wifi.WifiConfiguration() else android.net.wifi.WifiConfiguration(wc)
|
||||||
val original = wc?.toCompat()
|
val original = wc?.toCompat()
|
||||||
result.SSID = ssid
|
result.SSID = ssid?.toString()
|
||||||
result.preSharedKey = passphrase
|
result.preSharedKey = passphrase
|
||||||
result.hiddenSSID = isHiddenSsid
|
result.hiddenSSID = isHiddenSsid
|
||||||
apBand.setInt(result, when (band) {
|
apBand.setInt(result, when (band) {
|
||||||
@@ -520,7 +526,9 @@ data class SoftApConfigurationCompat(
|
|||||||
fun toPlatform(): SoftApConfiguration {
|
fun toPlatform(): SoftApConfiguration {
|
||||||
val sac = underlying as? SoftApConfiguration
|
val sac = underlying as? SoftApConfiguration
|
||||||
val builder = if (sac == null) classBuilder.newInstance() else newBuilder.newInstance(sac)
|
val builder = if (sac == null) classBuilder.newInstance() else newBuilder.newInstance(sac)
|
||||||
setSsid(builder, ssid)
|
if (Build.VERSION.SDK_INT >= 33) {
|
||||||
|
setWifiSsid(builder, ssid?.toPlatform())
|
||||||
|
} else setSsid(builder, ssid?.toString())
|
||||||
setPassphrase(builder, when (securityType) {
|
setPassphrase(builder, when (securityType) {
|
||||||
SoftApConfiguration.SECURITY_TYPE_OPEN,
|
SoftApConfiguration.SECURITY_TYPE_OPEN,
|
||||||
SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION,
|
SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION,
|
||||||
@@ -581,7 +589,6 @@ data class SoftApConfigurationCompat(
|
|||||||
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/4a5ff58/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java#161
|
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/4a5ff58/src/com/android/settings/wifi/dpp/WifiNetworkConfig.java#161
|
||||||
*/
|
*/
|
||||||
fun toQrCode() = StringBuilder("WIFI:").apply {
|
fun toQrCode() = StringBuilder("WIFI:").apply {
|
||||||
fun String.sanitize() = qrSanitizer.replace(this) { "\\${it.groupValues[1]}" }
|
|
||||||
when (securityType) {
|
when (securityType) {
|
||||||
SoftApConfiguration.SECURITY_TYPE_OPEN, SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION,
|
SoftApConfiguration.SECURITY_TYPE_OPEN, SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION,
|
||||||
SoftApConfiguration.SECURITY_TYPE_WPA3_OWE -> { }
|
SoftApConfiguration.SECURITY_TYPE_WPA3_OWE -> { }
|
||||||
@@ -592,11 +599,11 @@ data class SoftApConfigurationCompat(
|
|||||||
else -> throw IllegalArgumentException("Unsupported authentication type")
|
else -> throw IllegalArgumentException("Unsupported authentication type")
|
||||||
}
|
}
|
||||||
append("S:")
|
append("S:")
|
||||||
append(ssid!!.sanitize())
|
append(ssid!!.toMeCard())
|
||||||
append(';')
|
append(';')
|
||||||
passphrase?.let { passphrase ->
|
passphrase?.let { passphrase ->
|
||||||
append("P:")
|
append("P:")
|
||||||
append(passphrase.sanitize())
|
append(WifiSsidCompat.toMeCard(passphrase))
|
||||||
append(';')
|
append(';')
|
||||||
}
|
}
|
||||||
if (isHiddenSsid) append("H:true;")
|
if (isHiddenSsid) append("H:true;")
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import be.mygod.vpnhotspot.util.QRCodeDialog
|
|||||||
import be.mygod.vpnhotspot.util.RangeInput
|
import be.mygod.vpnhotspot.util.RangeInput
|
||||||
import be.mygod.vpnhotspot.util.readableMessage
|
import be.mygod.vpnhotspot.util.readableMessage
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
@@ -129,6 +130,14 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
override val ret get() = Arg(generateConfig())
|
override val ret get() = Arg(generateConfig())
|
||||||
|
private val hexToggleable get() = if (arg.p2pMode) !RepeaterService.safeMode else Build.VERSION.SDK_INT >= 33
|
||||||
|
private var hexSsid = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
dialogView.ssidWrapper.setEndIconActivated(value)
|
||||||
|
}
|
||||||
|
private val ssid get() =
|
||||||
|
if (hexSsid) WifiSsidCompat.fromHex(dialogView.ssid.text) else WifiSsidCompat.fromUtf8Text(dialogView.ssid.text)
|
||||||
|
|
||||||
private fun generateChannels() = SparseIntArray(2).apply {
|
private fun generateChannels() = SparseIntArray(2).apply {
|
||||||
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 31) {
|
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 31) {
|
||||||
@@ -137,7 +146,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
(dialogView.bandPrimary.selectedItem as ChannelOption).apply { put(band, channel) }
|
(dialogView.bandPrimary.selectedItem as ChannelOption).apply { put(band, channel) }
|
||||||
}
|
}
|
||||||
private fun generateConfig(full: Boolean = true) = base.copy(
|
private fun generateConfig(full: Boolean = true) = base.copy(
|
||||||
ssid = dialogView.ssid.text.toString(),
|
ssid = ssid,
|
||||||
passphrase = if (dialogView.password.length() != 0) dialogView.password.text.toString() else null).apply {
|
passphrase = if (dialogView.password.length() != 0) dialogView.password.text.toString() else null).apply {
|
||||||
if (!arg.p2pMode) {
|
if (!arg.p2pMode) {
|
||||||
securityType = dialogView.security.selectedItemPosition
|
securityType = dialogView.security.selectedItemPosition
|
||||||
@@ -189,7 +198,31 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
setNegativeButton(R.string.donations__button_close, null)
|
setNegativeButton(R.string.donations__button_close, null)
|
||||||
dialogView.toolbar.inflateMenu(R.menu.toolbar_configuration)
|
dialogView.toolbar.inflateMenu(R.menu.toolbar_configuration)
|
||||||
dialogView.toolbar.setOnMenuItemClickListener(this@WifiApDialogFragment)
|
dialogView.toolbar.setOnMenuItemClickListener(this@WifiApDialogFragment)
|
||||||
dialogView.ssidWrapper.setLengthCounter { it.toString().toByteArray().size }
|
dialogView.ssidWrapper.setLengthCounter {
|
||||||
|
try {
|
||||||
|
ssid?.bytes?.size ?: 0
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hexToggleable) dialogView.ssidWrapper.apply {
|
||||||
|
endIconMode = TextInputLayout.END_ICON_CUSTOM
|
||||||
|
setEndIconOnClickListener {
|
||||||
|
val ssid = try {
|
||||||
|
ssid
|
||||||
|
} catch (_: IllegalArgumentException) {
|
||||||
|
return@setEndIconOnClickListener
|
||||||
|
}
|
||||||
|
val newText = if (hexSsid) ssid?.run {
|
||||||
|
decode().also { if (it == null) return@setEndIconOnClickListener }
|
||||||
|
} else ssid?.hex
|
||||||
|
hexSsid = !hexSsid
|
||||||
|
dialogView.ssid.setText(newText)
|
||||||
|
}
|
||||||
|
findViewById<View>(com.google.android.material.R.id.text_input_end_icon).apply {
|
||||||
|
tooltipText = contentDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!arg.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment)
|
if (!arg.readOnly) dialogView.ssid.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
if (arg.p2pMode) dialogView.securityWrapper.isGone = true else dialogView.security.apply {
|
if (arg.p2pMode) dialogView.securityWrapper.isGone = true else dialogView.security.apply {
|
||||||
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0,
|
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0,
|
||||||
@@ -293,7 +326,14 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
} else selection
|
} else selection
|
||||||
}
|
}
|
||||||
private fun populateFromConfiguration() {
|
private fun populateFromConfiguration() {
|
||||||
dialogView.ssid.setText(base.ssid)
|
dialogView.ssid.setText(base.ssid.let { ssid ->
|
||||||
|
when {
|
||||||
|
ssid == null -> null
|
||||||
|
hexSsid -> ssid.hex
|
||||||
|
hexToggleable -> ssid.decode() ?: ssid.hex.also { hexSsid = true }
|
||||||
|
else -> ssid.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
if (!arg.p2pMode) dialogView.security.setSelection(base.securityType)
|
if (!arg.p2pMode) dialogView.security.setSelection(base.securityType)
|
||||||
dialogView.password.setText(base.passphrase)
|
dialogView.password.setText(base.passphrase)
|
||||||
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
|
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
|
||||||
@@ -334,11 +374,18 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
|
|
||||||
private fun validate() {
|
private fun validate() {
|
||||||
if (!started) return
|
if (!started) return
|
||||||
val ssidLength = dialogView.ssid.text.toString().toByteArray().size
|
val (ssidOk, ssidError) = 0.let {
|
||||||
val ssidLengthOk = ssidLength in 1..32
|
val ssid = try {
|
||||||
dialogView.ssidWrapper.error = if (arg.p2pMode && RepeaterService.safeMode && ssidLength < 9) {
|
ssid
|
||||||
requireContext().getString(R.string.settings_service_repeater_safe_mode_warning)
|
} catch (e: IllegalArgumentException) {
|
||||||
} else if (ssidLengthOk) null else " "
|
return@let false to e.readableMessage
|
||||||
|
}
|
||||||
|
val ssidLength = ssid?.bytes?.size ?: 0
|
||||||
|
if (ssidLength in 1..32) true to if (arg.p2pMode && RepeaterService.safeMode && ssidLength < 9) {
|
||||||
|
requireContext().getString(R.string.settings_service_repeater_safe_mode_warning)
|
||||||
|
} else null else false to " "
|
||||||
|
}
|
||||||
|
dialogView.ssidWrapper.error = ssidError
|
||||||
val selectedSecurity = if (arg.p2pMode) {
|
val selectedSecurity = if (arg.p2pMode) {
|
||||||
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|
||||||
} else dialogView.security.selectedItemPosition
|
} else dialogView.security.selectedItemPosition
|
||||||
@@ -466,7 +513,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
bridgedTimeoutError == null && vendorElementsError == null && persistentRandomizedMacValid &&
|
bridgedTimeoutError == null && vendorElementsError == null && persistentRandomizedMacValid &&
|
||||||
acsNoError && bandwidthError == null
|
acsNoError && bandwidthError == null
|
||||||
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
|
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
|
||||||
ssidLengthOk && passwordValid && bandError == null && canCopy
|
ssidOk && passwordValid && bandError == null && canCopy
|
||||||
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
|
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
|
import android.net.wifi.WifiSsid
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.CharBuffer
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.CodingErrorAction
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class WifiSsidCompat(val bytes: ByteArray) : Parcelable {
|
||||||
|
companion object {
|
||||||
|
private val hexTester = Regex("^(?:[0-9a-f]{2})*$", RegexOption.IGNORE_CASE)
|
||||||
|
private val qrSanitizer = Regex("([\\\\\":;,])")
|
||||||
|
|
||||||
|
fun fromHex(hex: CharSequence?) = hex?.run {
|
||||||
|
require(length % 2 == 0) { "Input should be hex: $hex" }
|
||||||
|
WifiSsidCompat((0 until length / 2).map {
|
||||||
|
Integer.parseInt(substring(it * 2, it * 2 + 2), 16).toByte()
|
||||||
|
}.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract("null -> null; !null -> !null")
|
||||||
|
fun fromUtf8Text(text: CharSequence?) = text?.toString()?.toByteArray()?.let { WifiSsidCompat(it) }
|
||||||
|
|
||||||
|
fun toMeCard(text: String) = qrSanitizer.replace(text) { "\\${it.groupValues[1]}" }
|
||||||
|
|
||||||
|
@RequiresApi(33)
|
||||||
|
fun WifiSsid.toCompat() = WifiSsidCompat(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(bytes.size <= 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(31)
|
||||||
|
fun toPlatform() = WifiSsid.fromBytes(bytes)
|
||||||
|
|
||||||
|
fun decode(charset: Charset = Charsets.UTF_8) = CharBuffer.allocate(32).run {
|
||||||
|
val result = charset.newDecoder().apply {
|
||||||
|
onMalformedInput(CodingErrorAction.REPORT)
|
||||||
|
onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||||
|
}.decode(ByteBuffer.wrap(bytes), this, true)
|
||||||
|
if (result.isError) null else flip().toString()
|
||||||
|
}
|
||||||
|
val hex get() = bytes.joinToString("") { "%02x".format(it.toUByte().toInt()) }
|
||||||
|
|
||||||
|
fun toMeCard(): String {
|
||||||
|
val utf8 = decode() ?: return hex
|
||||||
|
return if (hexTester.matches(utf8)) "\"$utf8\"" else toMeCard(utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = String(bytes)
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
other as WifiSsidCompat
|
||||||
|
if (!bytes.contentEquals(other.bytes)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return bytes.contentHashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
5
mobile/src/main/res/drawable/ic_av_closed_caption.xml
Normal file
5
mobile/src/main/res/drawable/ic_av_closed_caption.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,11L9.5,11v-0.5h-2v3h2L9.5,13L11,13v1c0,0.55 -0.45,1 -1,1L7,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1zM18,11h-1.5v-0.5h-2v3h2L16.5,13L18,13v1c0,0.55 -0.45,1 -1,1h-3c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19.5,5.5v13h-15v-13h15zM19,4L5,4c-1.11,0 -2,0.9 -2,2v12c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM11,11L9.5,11v-0.5h-2v3h2L9.5,13L11,13v1c0,0.55 -0.45,1 -1,1L7,15c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1zM18,11h-1.5v-0.5h-2v3h2L16.5,13L18,13v1c0,0.55 -0.45,1 -1,1h-3c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h3c0.55,0 1,0.45 1,1v1z"/>
|
||||||
|
</vector>
|
||||||
5
mobile/src/main/res/drawable/toggle_hex.xml
Normal file
5
mobile/src/main/res/drawable/toggle_hex.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_av_closed_caption" android:state_activated="true"/>
|
||||||
|
<item android:drawable="@drawable/ic_av_closed_caption_off"/>
|
||||||
|
</selector>
|
||||||
@@ -49,6 +49,8 @@
|
|||||||
android:hint="@string/wifi_ssid"
|
android:hint="@string/wifi_ssid"
|
||||||
app:counterEnabled="true"
|
app:counterEnabled="true"
|
||||||
app:counterMaxLength="32"
|
app:counterMaxLength="32"
|
||||||
|
app:endIconContentDescription="@string/wifi_ssid_toggle_hex"
|
||||||
|
app:endIconDrawable="@drawable/toggle_hex"
|
||||||
app:errorEnabled="true">
|
app:errorEnabled="true">
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/ssid"
|
android:id="@+id/ssid"
|
||||||
|
|||||||
@@ -175,6 +175,7 @@
|
|||||||
<string name="configuration_share">使用 QR 码分享</string>
|
<string name="configuration_share">使用 QR 码分享</string>
|
||||||
<string name="configuration_rejected">Android 系统拒绝使用此配置。(详情参见日志)</string>
|
<string name="configuration_rejected">Android 系统拒绝使用此配置。(详情参见日志)</string>
|
||||||
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
|
<string name="wifi_ssid" msgid="5519636102673067319">"网络名称"</string>
|
||||||
|
<string name="wifi_ssid_toggle_hex">切换十六进制显示</string>
|
||||||
<string name="wifi_security" msgid="6603611185592956936">"安全性"</string>
|
<string name="wifi_security" msgid="6603611185592956936">"安全性"</string>
|
||||||
<string name="wifi_password" msgid="5948219759936151048">"密码"</string>
|
<string name="wifi_password" msgid="5948219759936151048">"密码"</string>
|
||||||
<string name="wifi_hotspot_auto_off">未连接任何设备时自动关闭 WLAN 热点</string>
|
<string name="wifi_hotspot_auto_off">未连接任何设备时自动关闭 WLAN 热点</string>
|
||||||
|
|||||||
@@ -199,6 +199,7 @@
|
|||||||
<string name="configuration_share">Share via QR code</string>
|
<string name="configuration_share">Share via QR code</string>
|
||||||
<string name="configuration_rejected">Android system refuses such configuration. (see logcat)</string>
|
<string name="configuration_rejected">Android system refuses such configuration. (see logcat)</string>
|
||||||
<string name="wifi_ssid">Network name</string>
|
<string name="wifi_ssid">Network name</string>
|
||||||
|
<string name="wifi_ssid_toggle_hex">Toggle hex display</string>
|
||||||
<string name="wifi_security">Security</string>
|
<string name="wifi_security">Security</string>
|
||||||
<string name="wifi_password">Password</string>
|
<string name="wifi_password">Password</string>
|
||||||
<string name="wifi_hotspot_auto_off">Turn off hotspot automatically when no devices are connected</string>
|
<string name="wifi_hotspot_auto_off">Turn off hotspot automatically when no devices are connected</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user