diff --git a/mobile/src/google/AndroidManifest.xml b/mobile/src/google/AndroidManifest.xml
index 973e237e..cba266f0 100644
--- a/mobile/src/google/AndroidManifest.xml
+++ b/mobile/src/google/AndroidManifest.xml
@@ -3,8 +3,6 @@
-
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index ae17b348..f7bfd47a 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -36,6 +36,8 @@
tools:ignore="ProtectedPermissions" />
+
+ get() = VendorElements.deserialize(app.pref.getString(KEY_VENDOR_ELEMENTS, null))
+ set(value) = app.pref.edit { putString(KEY_VENDOR_ELEMENTS, VendorElements.serialize(value)) }
}
enum class Status {
@@ -305,6 +314,31 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
} else SmartSnackbar.make(formatReason(R.string.repeater_set_oc_failure, reason)).show()
} else SmartSnackbar.make(R.string.repeater_failure_disconnected).show()
}
+ @RequiresApi(33)
+ private suspend fun setVendorElements(ve: List = vendorElements) {
+ val channel = channel
+ if (channel != null) {
+ val reason = try {
+ p2pManager.setVendorElements(channel, ve) ?: return
+ } catch (e: IllegalArgumentException) {
+ SmartSnackbar.make(getString(R.string.repeater_set_vendor_elements_failure, e.message)).show()
+ return
+ }
+ if (reason == WifiP2pManager.ERROR) {
+ val rootReason = try {
+ RootManager.use {
+ if (deinitPending.getAndSet(false)) it.execute(RepeaterCommands.Deinit())
+ it.execute(RepeaterCommands.SetVendorElements(ve))
+ } ?: return
+ } catch (e: Exception) {
+ Timber.w(e)
+ SmartSnackbar.make(e).show()
+ return
+ }
+ SmartSnackbar.make(formatReason(R.string.repeater_set_vendor_elements_failure, rootReason.value)).show()
+ } else SmartSnackbar.make(formatReason(R.string.repeater_set_vendor_elements_failure, reason)).show()
+ } else SmartSnackbar.make(R.string.repeater_failure_disconnected).show()
+ }
override fun onChannelDisconnected() {
channel = null
@@ -321,9 +355,10 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- if (!safeMode) when (key) {
- KEY_OPERATING_CHANNEL -> launch { setOperatingChannel() }
- KEY_SAFE_MODE -> deinitPending.set(true)
+ when (key) {
+ KEY_OPERATING_CHANNEL -> if (!safeMode) launch { setOperatingChannel() }
+ KEY_VENDOR_ELEMENTS -> if (Build.VERSION.SDK_INT >= 33) launch { setVendorElements() }
+ KEY_SAFE_MODE -> if (!safeMode) deinitPending.set(true)
}
}
@@ -405,6 +440,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
binder.fetchPersistentGroup()
setOperatingChannel()
}
+ if (Build.VERSION.SDK_INT >= 33) setVendorElements()
val networkName = networkName
val passphrase = passphrase
@SuppressLint("MissingPermission") // missing permission will simply leading to returning ERROR
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 0081be0a..c3bce869 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt
@@ -199,6 +199,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
macRandomizationSetting = if (WifiApManager.p2pMacRandomizationSupported) {
SoftApConfigurationCompat.RANDOMIZATION_NON_PERSISTENT
} else SoftApConfigurationCompat.RANDOMIZATION_NONE,
+ vendorElements = RepeaterService.vendorElements,
).apply {
bssid = RepeaterService.deviceAddress
setChannel(RepeaterService.operatingChannel, RepeaterService.operatingBand)
@@ -214,6 +215,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
macRandomizationSetting = if (WifiApManager.p2pMacRandomizationSupported) {
SoftApConfigurationCompat.RANDOMIZATION_NON_PERSISTENT
} else SoftApConfigurationCompat.RANDOMIZATION_NONE,
+ vendorElements = RepeaterService.vendorElements,
).run {
setChannel(RepeaterService.operatingChannel)
try {
@@ -258,5 +260,6 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
RepeaterService.operatingChannel = channel
RepeaterService.isAutoShutdownEnabled = config.isAutoShutdownEnabled
RepeaterService.shutdownTimeoutMillis = config.shutdownTimeoutMillis
+ RepeaterService.vendorElements = config.vendorElements
}
}
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/VendorElements.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/VendorElements.kt
new file mode 100644
index 00000000..9b527d29
--- /dev/null
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/VendorElements.kt
@@ -0,0 +1,27 @@
+package be.mygod.vpnhotspot.net.wifi
+
+import android.net.wifi.ScanResult
+import androidx.annotation.RequiresApi
+import timber.log.Timber
+
+@RequiresApi(33)
+object VendorElements {
+ fun serialize(input: List) = input.joinToString("\n") { element ->
+ element.bytes.let { buffer ->
+ StringBuilder().apply {
+ while (buffer.hasRemaining()) append("%02x".format(buffer.get()))
+ }.toString()
+ }.also {
+ if (element.id != 221 || element.idExt != 0 || it.isEmpty()) Timber.w(Exception(
+ "Unexpected InformationElement ${element.id}, ${element.idExt}, $it"))
+ }
+ }
+
+ fun deserialize(input: CharSequence?) = (input ?: "").split("\n").map { line ->
+ if (line.isBlank()) return@map null
+ require(line.length % 2 == 0) { "Input should be hex: $line" }
+ (0 until line.length / 2).map {
+ Integer.parseInt(line.substring(it * 2, it * 2 + 2), 16).toByte()
+ }.toByteArray()
+ }.filterNotNull().map { ScanResult.InformationElement(221, 0, it) }
+}
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 09606980..59e78a26 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
@@ -5,7 +5,6 @@ import android.annotation.TargetApi
import android.content.ClipData
import android.content.ClipDescription
import android.content.DialogInterface
-import android.net.wifi.ScanResult
import android.net.wifi.SoftApConfiguration
import android.os.Build
import android.os.Parcelable
@@ -143,13 +142,6 @@ class WifiApDialogFragment : AlertDialogFragment
- if (line.isBlank()) return@map null
- require(line.length % 2 == 0) { "Input should be hex: $line" }
- (0 until line.length / 2).map {
- Integer.parseInt(line.substring(it * 2, it * 2 + 2), 16).toByte()
- }.toByteArray()
- }.filterNotNull().map { ScanResult.InformationElement(221, 0, it) }
private fun generateConfig(full: Boolean = true) = base.copy(
ssid = dialogView.ssid.text.toString(),
passphrase = if (dialogView.password.length() != 0) dialogView.password.text.toString() else null).apply {
@@ -182,12 +174,14 @@ class WifiApDialogFragment : AlertDialogFragment
if (text.isNullOrEmpty()) -1L else text.toString().toLong()
}
- vendorElements = generateVendorElements()
+ vendorElements = VendorElements.deserialize(dialogView.vendorElements.text)
persistentRandomizedMacAddress = if (dialogView.persistentRandomizedMac.length() != 0) {
MacAddressCompat.fromString(dialogView.persistentRandomizedMac.text.toString()).toPlatform()
} else null
allowedAcsChannels = acsList.associate { (band, text, _) -> band to RangeInput.fromString(text.text) }
- maxChannelBandwidth = (dialogView.maxChannelBandwidth.selectedItem as BandWidth).width
+ if (!arg.p2pMode && Build.VERSION.SDK_INT >= 33) {
+ maxChannelBandwidth = (dialogView.maxChannelBandwidth.selectedItem as BandWidth).width
+ }
}
}
@@ -332,16 +326,7 @@ class WifiApDialogFragment : AlertDialogFragment
- element.bytes.let { buffer ->
- StringBuilder().apply {
- while (buffer.hasRemaining()) append("%02x".format(buffer.get()))
- }.toString()
- }.also {
- if (element.id != 221 || element.idExt != 0 || it.isEmpty()) Timber.w(Exception(
- "Unexpected InformationElement ${element.id}, ${element.idExt}, $it"))
- }
- })
+ dialogView.vendorElements.setText(VendorElements.serialize(base.vendorElements))
dialogView.persistentRandomizedMac.setText(base.persistentRandomizedMacAddress?.toString())
for ((band, text, _) in acsList) text.setText(RangeInput.toString(base.allowedAcsChannels[band]))
if (Build.VERSION.SDK_INT >= 33) bandWidthOptions.binarySearch(BandWidth(base.maxChannelBandwidth)).let {
@@ -448,7 +433,7 @@ class WifiApDialogFragment : AlertDialogFragment= 33) {
try {
- generateVendorElements().also {
+ VendorElements.deserialize(dialogView.vendorElements.text).also {
if (!arg.p2pMode) SoftApConfigurationCompat.testPlatformValidity(it)
}
null
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
index d4749c8e..49481ddf 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt
@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint
+import android.net.wifi.ScanResult
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pInfo
@@ -8,6 +9,7 @@ import android.net.wifi.p2p.WifiP2pManager
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.MacAddressCompat
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.util.callSuper
import be.mygod.vpnhotspot.util.matchesCompat
import kotlinx.coroutines.CompletableDeferred
@@ -54,6 +56,14 @@ object WifiP2pManagerHelper {
return result.future.await()
}
+ @RequiresApi(33)
+ suspend fun WifiP2pManager.setVendorElements(c: WifiP2pManager.Channel,
+ ve: List): Int? {
+ val result = ResultListener()
+ setVendorElements(c, ve, result)
+ return result.future.await()
+ }
+
/**
* Available since Android 4.3.
*
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt
index 0e84a5eb..89a4da0c 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/RepeaterCommands.kt
@@ -1,5 +1,6 @@
package be.mygod.vpnhotspot.root
+import android.net.wifi.ScanResult
import android.net.wifi.p2p.WifiP2pManager
import android.os.Looper
import android.os.Parcelable
@@ -11,6 +12,7 @@ import be.mygod.librootkotlinx.*
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestDeviceAddress
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo
+import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setVendorElements
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.util.Services
import kotlinx.parcelize.Parcelize
@@ -55,6 +57,14 @@ object RepeaterCommands {
}
}
+ @Parcelize
+ @RequiresApi(33)
+ data class SetVendorElements(private val ve: List) : RootCommand {
+ override suspend fun execute() = Services.p2p!!.run {
+ setVendorElements(obtainChannel(), ve)?.let { ParcelableInt(it) }
+ }
+ }
+
@Parcelize
data class WriteP2pConfig(val data: String, val legacy: Boolean) : RootCommandNoResult {
override suspend fun execute(): Parcelable? {
diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml
index de43fe29..ca87b40a 100644
--- a/mobile/src/main/res/values-zh-rCN/strings.xml
+++ b/mobile/src/main/res/values-zh-rCN/strings.xml
@@ -23,6 +23,7 @@
关闭已有 P2P 群组失败(原因:%s)
关闭 P2P 群组失败(原因:%s)
设置运行频段失败(原因:%s)
+ 设置供应商特定元素失败(原因:%s)
内部异常
设备不支持 Wi\u2011Fi 直连
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index c2956e8e..d4b5aa99 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -40,6 +40,7 @@
Failed to remove P2P group (reason: %s)
Failed to remove old P2P group (reason: %s)
Failed to set operating channel (reason: %s)
+ Failed to set vendor elements (reason: %s)
internal error
Wi\u2011Fi direct unsupported