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