From 7f78c9da68f0b5f2c09b88a4a1b4d4dc23bb5d97 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 01:37:15 -0400 Subject: [PATCH 001/285] Prevent calling keyIterator --- .../net/wifi/SoftApConfigurationCompat.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 59a60d05..1fdb73db 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,7 +9,6 @@ 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 @@ -351,9 +350,10 @@ data class SoftApConfigurationCompat( } fun getChannel(band: Int): Int { var result = -1 - for (b in channels.keyIterator()) if (band and b == band) { + repeat(channels.size()) { i -> + if (band and channels.keyAt(i) != band) return@repeat require(result == -1) { "Duplicate band found" } - result = channels[b] + result = channels.valueAt(i) } return result } @@ -363,9 +363,12 @@ data class SoftApConfigurationCompat( 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 + repeat(channels.size()) { i -> if (channels.valueAt(i) == 0) setBand = setBand or channels.keyAt(i) } 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]) + repeat(channels.size()) { i -> + val band = channels.keyAt(i) + if (band and setBand == 0) put(band, channels.valueAt(i)) + } } } From 453d6bb43016213e42af187cf78a2ac87ab4e124 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 01:52:00 -0400 Subject: [PATCH 002/285] Make declaration of bandLookup explicit of API requirements --- .../be/mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 1fdb73db..4f3429a3 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 @@ -71,7 +71,8 @@ data class SoftApConfigurationCompat( } intArrayOf(BAND_2GHZ, BAND_5GHZ, BAND_6GHZ, BAND_60GHZ) } - val bandLookup = ConstantLookup("BAND_", null, "2GHZ", "5GHZ") + @RequiresApi(31) + val bandLookup = ConstantLookup("BAND_") @TargetApi(31) const val RANDOMIZATION_NONE = 0 From f7e701199250f25b0f029759222944e0e414e488 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 01:55:39 -0400 Subject: [PATCH 003/285] Update translations --- mobile/src/main/res/values-ru/strings.xml | 4 +++- mobile/src/main/res/values-zh-rTW/strings.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml index 09fb0188..c74ace52 100644 --- a/mobile/src/main/res/values-ru/strings.xml +++ b/mobile/src/main/res/values-ru/strings.xml @@ -61,7 +61,9 @@ "Диапазон частот Wi-Fi" "Авто" "2,4 ГГц" - "5,0 ГГц" + "5 ГГц" + "6 ГГц" + "60 ГГц" "MAC-адрес" "Скрытая сеть" "Сохранить" diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index ac37f4b9..615d40be 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -176,7 +176,7 @@ 關閉延遲時間 默認延遲:%d 毫秒 AP 頻帶 - Disabled + 停用 自動 2.4 GHz 頻帶 5 GHz 頻帶 From 0a2e13556a7b9294eb33d1d8d76f63a8b4ff5829 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 02:07:04 -0400 Subject: [PATCH 004/285] Optimize usage of SparseArray --- .../main/java/be/mygod/librootkotlinx/RootServer.kt | 9 ++++----- .../vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 12 ++++++------ .../vpnhotspot/net/wifi/WifiApDialogFragment.kt | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt index 845f825b..38f5a574 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt @@ -8,7 +8,6 @@ import android.os.RemoteException import android.system.Os import android.system.OsConstants import androidx.collection.LongSparseArray -import androidx.collection.set import androidx.collection.valueIterator import kotlinx.coroutines.* import kotlinx.coroutines.channels.* @@ -248,7 +247,7 @@ class RootServer { @Suppress("UNCHECKED_CAST") val callback = Callback.Ordinary(this, counter, classLoader, future as CompletableDeferred) if (active) { - callbackLookup[counter] = callback + callbackLookup.append(counter, callback) sendLocked(command) } else future.cancel() callback @@ -279,7 +278,7 @@ class RootServer { @Suppress("UNCHECKED_CAST") val callback = Callback.Channel(this@RootServer, counter, classLoader, this as SendChannel) if (active) { - callbackLookup[counter] = callback + callbackLookup.append(counter, callback) sendLocked(command) } else callback.finish.cancel() callback @@ -434,7 +433,7 @@ class RootServer { } is RootCommand<*> -> { val commandJob = Job() - cancellables[callback] = { commandJob.cancel() } + cancellables.append(callback) { commandJob.cancel() } defaultWorker.launch(commandJob) { val result = try { val result = command.execute(); @@ -452,7 +451,7 @@ class RootServer { val result = try { coroutineScope { command.create(this).also { - cancellables[callback] = { it.cancel() } + cancellables.append(callback) { it.cancel() } }.consumeEach { result -> withContext(callbackWorker) { output.pushResult(callback, result) } } 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 4f3429a3..b6316fb6 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 @@ -32,7 +32,7 @@ data class SoftApConfigurationCompat( * Otherwise, use [optimizeChannels] or [setChannel]. */ @TargetApi(23) - var channels: SparseIntArray = SparseIntArray(1).apply { put(BAND_2GHZ, 0) }, + var channels: SparseIntArray = SparseIntArray(1).apply { append(BAND_2GHZ, 0) }, var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN, @TargetApi(30) var maxNumberOfClients: Int = 0, @@ -281,12 +281,12 @@ data class SoftApConfigurationCompat( hiddenSSID, // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344 SparseIntArray(1).apply { - if (Build.VERSION.SDK_INT < 23) put(BAND_LEGACY, 0) else put(when (val band = apBand.getInt(this)) { + if (Build.VERSION.SDK_INT >= 23) append(when (val band = apBand.getInt(this)) { 0 -> BAND_2GHZ 1 -> BAND_5GHZ -1 -> BAND_LEGACY else -> throw IllegalArgumentException("Unexpected band $band") - }, apChannel.getInt(this)) + }, apChannel.getInt(this)) else append(BAND_LEGACY, 0) }, allowedKeyManagement.nextSetBit(0).let { selected -> require(allowedKeyManagement.nextSetBit(selected + 1) < 0) { @@ -319,7 +319,7 @@ data class SoftApConfigurationCompat( passphrase, isHiddenSsid, if (BuildCompat.isAtLeastS()) getChannels(this) as SparseIntArray else SparseIntArray(1).apply { - put(getBand(this) as Int, getChannel(this) as Int) + append(getBand(this) as Int, getChannel(this) as Int) }, securityType, getMaxNumberOfClients(this) as Int, @@ -359,13 +359,13 @@ data class SoftApConfigurationCompat( return result } fun setChannel(channel: Int, band: Int = BAND_LEGACY) { - channels = SparseIntArray(1).apply { put(band, channel) } + channels = SparseIntArray(1).apply { append(band, channel) } } fun optimizeChannels(channels: SparseIntArray = this.channels) { this.channels = SparseIntArray(channels.size()).apply { var setBand = 0 repeat(channels.size()) { i -> if (channels.valueAt(i) == 0) setBand = setBand or channels.keyAt(i) } - if (setBand != 0) put(setBand, 0) // merge all bands into one + if (setBand != 0) append(setBand, 0) // merge all bands into one repeat(channels.size()) { i -> val band = channels.keyAt(i) if (band and setBand == 0) put(band, channels.valueAt(i)) 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 2715258f..02db97d3 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 @@ -115,7 +115,7 @@ class WifiApDialogFragment : AlertDialogFragment= 0) channels.put(band, channel) + if (channel != null && channel >= 0) channels.append(band, channel) } if (!arg.p2pMode && BuildCompat.isAtLeastS() && dialogView.bridgedMode.isChecked) { this.channels = channels From 8e33ab77d7306b44b1e97c2a91f2d67047b0d9fe Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 23:35:24 +0800 Subject: [PATCH 005/285] Fix this overriding --- .../mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 b6316fb6..1f92e1bb 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 @@ -280,13 +280,13 @@ data class SoftApConfigurationCompat( preSharedKey, hiddenSSID, // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344 - SparseIntArray(1).apply { - if (Build.VERSION.SDK_INT >= 23) append(when (val band = apBand.getInt(this)) { + SparseIntArray(1).also { + if (Build.VERSION.SDK_INT >= 23) it.append(when (val band = apBand.getInt(this)) { 0 -> BAND_2GHZ 1 -> BAND_5GHZ -1 -> BAND_LEGACY else -> throw IllegalArgumentException("Unexpected band $band") - }, apChannel.getInt(this)) else append(BAND_LEGACY, 0) + }, apChannel.getInt(this)) else it.append(BAND_LEGACY, 0) }, allowedKeyManagement.nextSetBit(0).let { selected -> require(allowedKeyManagement.nextSetBit(selected + 1) < 0) { From c3f5f43b02bfc9679b78db46e4ef96bf2a056a1f Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 13:25:01 -0400 Subject: [PATCH 006/285] Add debug for readUnexpectedStderr --- mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt index 38f5a574..47559b39 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt @@ -101,6 +101,7 @@ class RootServer { private fun readUnexpectedStderr(): String? { if (!this::process.isInitialized) return null + Logger.me.d("Attempting to read stderr") var available = process.errorStream.available() return if (available <= 0) null else String(ByteArrayOutputStream().apply { try { From 2ac04bc1fa6eb8531b2e51893823954dbdcb4456 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 13:38:51 -0400 Subject: [PATCH 007/285] Use MaterialAlertDialogBuilder --- .../src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt index c20b1ace..0f607081 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/AlertDialogFragment.kt @@ -10,6 +10,7 @@ import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResultListener +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.parcelize.Parcelize /** @@ -44,7 +45,7 @@ abstract class AlertDialogFragment : fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey) override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = - AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create() + MaterialAlertDialogBuilder(requireContext()).also { it.prepare(this) }.create() override fun onClick(dialog: DialogInterface?, which: Int) { setFragmentResult(resultKey ?: return, Bundle().apply { From bfedbfe614d0b98287363c4dec7bbdd68c368395 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 19:37:08 -0400 Subject: [PATCH 008/285] Set p2p unsafe channel properly --- .../vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 1f92e1bb..657a9090 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 @@ -359,7 +359,13 @@ data class SoftApConfigurationCompat( return result } fun setChannel(channel: Int, band: Int = BAND_LEGACY) { - channels = SparseIntArray(1).apply { append(band, channel) } + channels = SparseIntArray(1).apply { + append(when { + channel <= 0 || band != BAND_LEGACY -> band + channel > 14 -> BAND_5GHZ + else -> BAND_2GHZ + }, channel) + } } fun optimizeChannels(channels: SparseIntArray = this.channels) { this.channels = SparseIntArray(channels.size()).apply { From 835de089004866467e42116465c840477b450086 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 20:07:07 -0400 Subject: [PATCH 009/285] Listen for onBlockedClientConnecting whenever callback is registered --- .../mygod/vpnhotspot/manage/TetherManager.kt | 16 ---------------- .../mygod/vpnhotspot/root/WifiApCommands.kt | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 3bb8b282..f2f9fa67 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -2,7 +2,6 @@ package be.mygod.vpnhotspot.manage import android.Manifest import android.annotation.TargetApi -import android.content.ClipData import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -175,21 +174,6 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), this.capability = capability data.notifyChange() } - @RequiresApi(30) - override fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) { - @Suppress("NAME_SHADOWING") - val client = WifiClient(client) - val macAddress = client.macAddress - var name = macAddress.toString() - if (BuildCompat.isAtLeastS()) client.apInstanceIdentifier?.let { name += "%$it" } - val reason = WifiApManager.clientBlockLookup(blockedReason, true) - Timber.i("$name blocked from connecting: $reason ($blockedReason)") - SmartSnackbar.make(parent.getString(R.string.tethering_manage_wifi_client_blocked, name, reason)).apply { - action(R.string.tethering_manage_wifi_copy_mac) { - app.clipboard.setPrimaryClip(ClipData.newPlainText(null, macAddress.toString())) - } - }.show() - } override val title get() = parent.getString(R.string.tethering_manage_wifi) override val tetherType get() = TetherType.WIFI diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt index bea331cd..e05e7f10 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt @@ -1,12 +1,18 @@ package be.mygod.vpnhotspot.root +import android.annotation.TargetApi +import android.content.ClipData import android.os.Parcelable import androidx.annotation.RequiresApi +import androidx.core.os.BuildCompat import be.mygod.librootkotlinx.ParcelableBoolean import be.mygod.librootkotlinx.RootCommand import be.mygod.librootkotlinx.RootCommandChannel +import be.mygod.vpnhotspot.App.Companion.app +import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat import be.mygod.vpnhotspot.net.wifi.WifiApManager +import be.mygod.vpnhotspot.net.wifi.WifiClient import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.coroutines.* import kotlinx.coroutines.channels.* @@ -119,6 +125,19 @@ object WifiApCommands { } is SoftApCallbackParcel.OnInfoChanged -> synchronized(callbacks) { lastCallback.info = parcel } is SoftApCallbackParcel.OnCapabilityChanged -> synchronized(callbacks) { lastCallback.capability = parcel } + is SoftApCallbackParcel.OnBlockedClientConnecting -> @TargetApi(30) { // passively consume events + val client = WifiClient(parcel.client) + val macAddress = client.macAddress + var name = macAddress.toString() + if (BuildCompat.isAtLeastS()) client.apInstanceIdentifier?.let { name += "%$it" } + val reason = WifiApManager.clientBlockLookup(parcel.blockedReason, true) + Timber.i("$name blocked from connecting: $reason (${parcel.blockedReason})") + SmartSnackbar.make(app.getString(R.string.tethering_manage_wifi_client_blocked, name, reason)).apply { + action(R.string.tethering_manage_wifi_copy_mac) { + app.clipboard.setPrimaryClip(ClipData.newPlainText(null, macAddress.toString())) + } + }.show() + } } for (callback in synchronized(callbacks) { callbacks.toList() }) parcel.dispatch(callback) } From 96e4fe0e886f96fbb3fa0cc2a6e1d5ebb3b6aa16 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 31 May 2021 20:30:50 -0400 Subject: [PATCH 010/285] v2.12.0 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 89472845..6524a422 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 260 - versionName = "2.11.7" + versionCode = 270 + versionName = "2.12.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From a0b52f130f335331bff7a07465c5c3f6974eef9a Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 1 Jun 2021 09:32:00 +0800 Subject: [PATCH 011/285] Fix testOnly again --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index 6fb3e361..feb18146 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,6 +13,7 @@ android.databinding.incremental=true android.enableJetifier=true android.enableR8.fullMode=true android.enableResourceOptimizations=false +android.injected.testOnly=false android.useAndroidX=true kapt.incremental.apt=true org.gradle.jvmargs=-Xmx1536m From b8df744c899cd4b7a50c7f079f4efe5ee3f0229f Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 1 Jun 2021 09:32:14 +0800 Subject: [PATCH 012/285] Fix this overriding again --- mobile/build.gradle.kts | 2 +- mobile/src/main/AndroidManifest.xml | 6 +++--- .../mygod/vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 6524a422..2c768553 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,7 +24,7 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 270 + versionCode = 271 versionName = "2.12.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index e694330c..e4fa8a08 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -9,9 +9,6 @@ - @@ -24,6 +21,9 @@ + 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 657a9090..4e06acae 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 @@ -318,8 +318,8 @@ data class SoftApConfigurationCompat( bssid?.toCompat()?.addr, passphrase, isHiddenSsid, - if (BuildCompat.isAtLeastS()) getChannels(this) as SparseIntArray else SparseIntArray(1).apply { - append(getBand(this) as Int, getChannel(this) as Int) + if (BuildCompat.isAtLeastS()) getChannels(this) as SparseIntArray else SparseIntArray(1).also { + it.append(getBand(this) as Int, getChannel(this) as Int) }, securityType, getMaxNumberOfClients(this) as Int, From 6cb7f966d66ee62604057e6c4e15601629380b41 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 2 Jun 2021 12:07:33 +0800 Subject: [PATCH 013/285] v2.12.1 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 2c768553..341b3b0c 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 271 - versionName = "2.12.0" + versionCode = 272 + versionName = "2.12.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From 301741dc42acd1d87cb05a3b3f5bc28491b8a398 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 1 Jun 2021 00:04:44 -0400 Subject: [PATCH 014/285] Fix crash on Android 11 --- .../vpnhotspot/net/wifi/SoftApConfigurationCompat.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 4e06acae..97b18594 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 @@ -328,10 +328,10 @@ data class SoftApConfigurationCompat( isClientControlByUserEnabled(this) as Boolean, getBlockedClientList(this) as List, getAllowedClientList(this) as List, - getMacRandomizationSetting(this) as Int, - isBridgedModeOpportunisticShutdownEnabled(this) as Boolean, - isIeee80211axEnabled(this) as Boolean, - isUserConfiguration(this) as Boolean, + if (BuildCompat.isAtLeastS()) getMacRandomizationSetting(this) as Int else RANDOMIZATION_PERSISTENT, + !BuildCompat.isAtLeastS() || isBridgedModeOpportunisticShutdownEnabled(this) as Boolean, + !BuildCompat.isAtLeastS() || isIeee80211axEnabled(this) as Boolean, + !BuildCompat.isAtLeastS() || isUserConfiguration(this) as Boolean, this) } From 8e9808895feaa403418da2e49a59098083c710df Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 00:50:53 -0400 Subject: [PATCH 015/285] Update dependencies --- build.gradle.kts | 2 +- mobile/build.gradle.kts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index eb766806..657363fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ buildscript { dependencies { classpath(kotlin("gradle-plugin", "1.5.10")) classpath("com.android.tools.build:gradle:7.0.0-beta03") - classpath("com.google.firebase:firebase-crashlytics-gradle:2.6.1") + classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.0") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") classpath("com.google.gms:google-services:4.3.8") } diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 73faba57..773ad12d 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -72,7 +72,7 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation("androidx.appcompat:appcompat:1.3.0") // https://issuetracker.google.com/issues/151603528 implementation("androidx.browser:browser:1.3.0") - implementation("androidx.core:core-ktx:1.6.0-beta01") + implementation("androidx.core:core-ktx:1.6.0-beta02") implementation("androidx.emoji:emoji:1.1.0") implementation("androidx.fragment:fragment-ktx:1.3.4") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") @@ -83,9 +83,9 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.android.billingclient:billing-ktx:4.0.0") implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") - implementation("com.google.android.material:material:1.4.0-beta01") + implementation("com.google.android.material:material:1.4.0-rc01") implementation("com.google.firebase:firebase-analytics-ktx:19.0.0") - implementation("com.google.firebase:firebase-crashlytics:18.0.0") + implementation("com.google.firebase:firebase-crashlytics:18.0.1") implementation("com.google.zxing:core:3.4.1") implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.linkedin.dexmaker:dexmaker:2.28.1") From 880843c8cbeb41c5ca4d3d66faf4d9fd4b52686b Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 01:25:07 -0400 Subject: [PATCH 016/285] Fix repeater stuck when Location is off on Android 11+ --- .../src/main/java/be/mygod/vpnhotspot/App.kt | 2 + .../be/mygod/vpnhotspot/RepeaterService.kt | 78 +++++++++++++------ .../manage/LocalOnlyHotspotManager.kt | 2 +- .../net/wifi/WifiP2pManagerHelper.kt | 11 ++- mobile/src/main/res/values-zh-rCN/strings.xml | 2 + mobile/src/main/res/values/strings.xml | 3 + 6 files changed, 73 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index 714a162e..9e2eca05 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.Application import android.content.ClipboardManager import android.content.res.Configuration +import android.location.LocationManager import android.os.Build import android.util.Log import androidx.annotation.Size @@ -124,6 +125,7 @@ class App : Application() { } val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) } val clipboard by lazy { getSystemService()!! } + val location by lazy { getSystemService() } val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") } val customTabsIntent by lazy { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 683848b0..fc386f1c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -6,6 +6,7 @@ import android.app.Service import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.location.LocationManager import android.net.wifi.WpsInfo import android.net.wifi.p2p.* import android.os.Build @@ -20,7 +21,10 @@ import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestConnectionInfo import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestDeviceAddress +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestGroupInfo +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestP2pState import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps @@ -212,6 +216,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onP2pConnectionChanged( intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO), intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)) + LocationManager.MODE_CHANGED_ACTION -> @TargetApi(30) { + onLocationModeChanged(intent.getBooleanExtra(LocationManager.EXTRA_LOCATION_ENABLED, false)) + } } } private val deviceListener = broadcastReceiver { _, intent -> @@ -308,6 +315,30 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } } + private var p2pPoller: Job? = null + @RequiresApi(30) + private fun onLocationModeChanged(enabled: Boolean) = if (enabled) p2pPoller?.cancel() else { + SmartSnackbar.make(R.string.repeater_location_off).apply { + action(R.string.repeater_location_off_configure) { + it.context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) + } + }.show() + p2pPoller = launch(start = CoroutineStart.UNDISPATCHED) { + while (true) { + delay(1000) + val channel = channel ?: return@launch + coroutineScope { + launch(start = CoroutineStart.UNDISPATCHED) { + if (p2pManager.requestP2pState(channel) == WifiP2pManager.WIFI_P2P_STATE_DISABLED) cleanLocked() + } + val info = async(start = CoroutineStart.UNDISPATCHED) { p2pManager.requestConnectionInfo(channel) } + val group = p2pManager.requestGroupInfo(channel) + onP2pConnectionChanged(info.await(), group) + } + } + } + } + /** * startService Step 1 */ @@ -318,29 +349,25 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene // bump self to foreground location service (API 29+) to use location later, also to avoid getting killed if (Build.VERSION.SDK_INT >= 26) showNotification() launch { - registerReceiver(receiver, intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, - WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) + val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + if (Build.VERSION.SDK_INT >= 30) filter.addAction(LocationManager.MODE_CHANGED_ACTION) + registerReceiver(receiver, filter) receiverRegistered = true - try { - p2pManager.requestGroupInfo(channel) { - when { - it == null -> doStart() - it.isGroupOwner -> launch { if (routingManager == null) doStartLocked(it) } - else -> { - Timber.i("Removing old group ($it)") - p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { - override fun onSuccess() { - doStart() - } - override fun onFailure(reason: Int) = - startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason)) - }) + val group = p2pManager.requestGroupInfo(channel) + when { + group == null -> doStart() + group.isGroupOwner -> if (routingManager == null) doStartLocked(group) + else -> { + Timber.i("Removing old group ($group)") + p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + launch { doStart() } } - } + override fun onFailure(reason: Int) = + startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason)) + }) } - } catch (e: SecurityException) { - Timber.w(e) - startFailure(e.readableMessage) } } return START_NOT_STICKY @@ -348,15 +375,19 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene /** * startService Step 2 (if a group isn't already available) */ - private fun doStart() = launch { + private suspend fun doStart() { val listener = object : WifiP2pManager.ActionListener { override fun onFailure(reason: Int) { startFailure(formatReason(R.string.repeater_create_group_failure, reason), showWifiEnable = reason == WifiP2pManager.BUSY) } - override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 + override fun onSuccess() { + // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 + // in order for this to happen, we need to make sure that the callbacks are firing + if (Build.VERSION.SDK_INT >= 30) onLocationModeChanged(app.location?.isLocationEnabled == true) + } } - val channel = channel ?: return@launch listener.onFailure(WifiP2pManager.BUSY) + val channel = channel ?: return listener.onFailure(WifiP2pManager.BUSY) if (!safeMode) { binder.fetchPersistentGroup() setOperatingChannel() @@ -466,6 +497,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene private fun cleanLocked() { if (receiverRegistered) { ensureReceiverUnregistered(receiver) + p2pPoller?.cancel() receiverRegistered = false } if (Build.VERSION.SDK_INT >= 28) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index 75906a0c..b42d5e24 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -66,7 +66,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager() if (if (Build.VERSION.SDK_INT < 28) @Suppress("DEPRECATION") { Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_OFF - } else context.getSystemService()?.isLocationEnabled != true) try { + } else app.location?.isLocationEnabled != true) try { context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show() } catch (e: ActivityNotFoundException) { 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 e43dce63..2f26d7a3 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 @@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.net.wifi import android.annotation.SuppressLint import android.net.wifi.WpsInfo import android.net.wifi.p2p.WifiP2pGroup +import android.net.wifi.p2p.WifiP2pInfo import android.net.wifi.p2p.WifiP2pManager import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.App.Companion.app @@ -128,7 +129,9 @@ object WifiP2pManagerHelper { return result.await() } - @SuppressLint("MissingPermission") + suspend fun WifiP2pManager.requestConnectionInfo(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestConnectionInfo(c) { complete(it) } }.await() + @SuppressLint("MissingPermission") // missing permission simply leads to null result @RequiresApi(29) suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? { val future = CompletableDeferred() @@ -138,4 +141,10 @@ object WifiP2pManagerHelper { if (address == MacAddressCompat.ANY_ADDRESS) null else address } } + @SuppressLint("MissingPermission") // missing permission simply leads to null result + suspend fun WifiP2pManager.requestGroupInfo(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestGroupInfo(c) { complete(it) } }.await() + @RequiresApi(29) + suspend fun WifiP2pManager.requestP2pState(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestP2pState(c) { complete(it) } }.await() } diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 69f3e8fc..e727ef85 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -29,6 +29,8 @@ 不支持此操作 服务不可用,请稍后重试 无线中继需要精确位置权限 + 由于系统限制,关闭位置信息服务可能产生问题并导致续航缩短 + 进入设置 临时 WLAN 热点 使用临时热点需要打开位置服务。 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index c4ce4b36..4505061f 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -47,6 +47,9 @@ Service unavailable. Try again later Repeater requires permissions for accessing fine location + Due to system restrictions, turning Location off may lead to things not working + properly and increased battery usage + Configure Temporary Wi\u2011Fi hotspot Temporary hotspot requires location to be turned on. From 02e7f06e61b54322532e3e64f41b7d95cb0ff519 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 02:06:06 -0400 Subject: [PATCH 017/285] Remove unnecessary try-catch --- .../be/mygod/vpnhotspot/RepeaterService.kt | 80 +++++++++---------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index fc386f1c..e7f03c16 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -394,41 +394,39 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } val networkName = networkName val passphrase = passphrase - try { - if (!safeMode || networkName == null || passphrase == null) { - persistNextGroup = true - p2pManager.createGroup(channel, listener) - } else @TargetApi(29) { - p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply { + @SuppressLint("MissingPermission") // missing permission will simply leading to returning ERROR + if (!safeMode || networkName == null || passphrase == null) { + persistNextGroup = true + p2pManager.createGroup(channel, listener) + } else @TargetApi(29) { + p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply { + try { + mNetworkName.set(this, networkName) // bypass networkName check + } catch (e: ReflectiveOperationException) { + Timber.w(e) try { - mNetworkName.set(this, networkName) // bypass networkName check - } catch (e: ReflectiveOperationException) { - Timber.w(e) setNetworkName(networkName) + } catch (e: IllegalArgumentException) { + Timber.w(e) + return startFailure(e.readableMessage) } - setPassphrase(passphrase) - when (val oc = operatingChannel) { - 0 -> setGroupOperatingBand(when (val band = operatingBand) { - SoftApConfigurationCompat.BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ - SoftApConfigurationCompat.BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ - else -> { - require(SoftApConfigurationCompat.isLegacyEitherBand(band)) { "Unknown band $band" } - WifiP2pConfig.GROUP_OWNER_BAND_AUTO - } - }) + } + setPassphrase(passphrase) + when (val oc = operatingChannel) { + 0 -> setGroupOperatingBand(when (val band = operatingBand) { + SoftApConfigurationCompat.BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ + SoftApConfigurationCompat.BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ else -> { - setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc)) + require(SoftApConfigurationCompat.isLegacyEitherBand(band)) { "Unknown band $band" } + WifiP2pConfig.GROUP_OWNER_BAND_AUTO } + }) + else -> { + setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc)) } - setDeviceAddress(deviceAddress?.toPlatform()) - }.build(), listener) - } - } catch (e: SecurityException) { - Timber.w(e) - startFailure(e.readableMessage) - } catch (e: IllegalArgumentException) { - Timber.w(e) - startFailure(e.readableMessage) + } + setDeviceAddress(deviceAddress?.toPlatform()) + }.build(), listener) } } /** @@ -481,19 +479,17 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene private fun showNotification(group: WifiP2pGroup? = null) = ServiceNotification.startForeground(this, if (group == null) emptyMap() else mapOf(Pair(group.`interface`, group.clientList?.size ?: 0))) - private fun removeGroup() { - p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { - override fun onSuccess() { - launch { cleanLocked() } - } - override fun onFailure(reason: Int) { - if (reason != WifiP2pManager.BUSY) { - SmartSnackbar.make(formatReason(R.string.repeater_remove_group_failure, reason)).show() - } // else assuming it's already gone - launch { cleanLocked() } - } - }) - } + private fun removeGroup() = p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + launch { cleanLocked() } + } + override fun onFailure(reason: Int) { + if (reason != WifiP2pManager.BUSY) { + SmartSnackbar.make(formatReason(R.string.repeater_remove_group_failure, reason)).show() + } // else assuming it's already gone + onSuccess() + } + }) private fun cleanLocked() { if (receiverRegistered) { ensureReceiverUnregistered(receiver) From 5b5cef9f7fb28cc68c6cd211943888d3ed2c246b Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 02:26:33 -0400 Subject: [PATCH 018/285] Init current upstream always --- .../java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt index a5b2056a..5ab3df9b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/UpstreamsPreference.kt @@ -60,6 +60,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co init { (context as LifecycleOwner).lifecycle.addObserver(this) + onUpdate() } override fun onStart(owner: LifecycleOwner) { From 60c299a44a92f0044be71e90d45fbb7eafd6020c Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 01:41:34 -0400 Subject: [PATCH 019/285] Fix UnblockCentral not working lmao --- README.md | 3 ++- .../mygod/vpnhotspot/util/UnblockCentral.kt | 20 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a0fd2398..0936a5ce 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,8 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Lcom/android/server/wifi/WifiContext;->ACTION_RESOURCES_APK:Ljava/lang/String;` * (since API 29) `Lcom/android/server/wifi/p2p/WifiP2pServiceImpl;->ANONYMIZED_DEVICE_ADDRESS:Ljava/lang/String;` * (since API 30) `Lcom/android/server/SystemServer;->TETHERING_CONNECTOR_CLASS:Ljava/lang/String;` -* (since API 29) `Ldalvik/system/VMDebug;->allowHiddenApiReflectionFrom(Ljava/lang/Class;)V,unsupported` +* (since API 29) `Ldalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;,core-platform-api,greylist` +* (since API 29) `Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V,blacklist,core-platform-api` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->(Ljava/lang/Class;I)V,unsupported` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o` * (prior to API 29) `Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,max-target-p` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt index 0a9a7daa..d5b43755 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/UnblockCentral.kt @@ -3,8 +3,9 @@ package be.mygod.vpnhotspot.util import android.annotation.SuppressLint import android.net.wifi.SoftApConfiguration import android.net.wifi.p2p.WifiP2pConfig +import android.os.Build import androidx.annotation.RequiresApi -import timber.log.Timber +import java.lang.reflect.Method /** * The central object for accessing all the useful blocked APIs. Thanks Google! @@ -17,16 +18,15 @@ object UnblockCentral { /** * Retrieve this property before doing dangerous shit. */ - @get:RequiresApi(28) private val init by lazy { - try { - Class.forName("dalvik.system.VMDebug").getDeclaredMethod("allowHiddenApiReflectionFrom", Class::class.java) - .invoke(null, UnblockCentral::class.java) - true - } catch (e: ReflectiveOperationException) { - Timber.w(e) - false - } + if (Build.VERSION.SDK_INT < 28) return@lazy + // TODO: fix this not working when targeting API 30+ + val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", + String::class.java, arrayOf>()::class.java) + val clazz = Class.forName("dalvik.system.VMRuntime") + val setHiddenApiExemptions = getDeclaredMethod(clazz, "setHiddenApiExemptions", + arrayOf(Array::class.java)) as Method + setHiddenApiExemptions(clazz.getDeclaredMethod("getRuntime")(null), arrayOf("")) } @RequiresApi(31) From 2e5f5b61927234da23668b3e2086b43c59d6da67 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 01:52:14 -0400 Subject: [PATCH 020/285] Check for field type before attempting to read --- .../src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt index 0e18da97..135ebdfb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/ConstantLookup.kt @@ -12,7 +12,7 @@ class ConstantLookup(private val prefix: String, private val lookup29: Array().apply { for (field in clazz().declaredFields) try { - if (field.name.startsWith(prefix)) put(field.getInt(null), field.name) + if (field.type == Int::class.java && field.name.startsWith(prefix)) put(field.getInt(null), field.name) } catch (e: Exception) { Timber.w(e) } @@ -40,7 +40,7 @@ inline fun ConstantLookup(prefix: String, vararg lookup29: String?) class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) { private val lookup = LongSparseArray().apply { for (field in clazz.declaredFields) try { - if (field.name.startsWith(prefix)) put(field.getLong(null), field.name) + if (field.type == Long::class.java && field.name.startsWith(prefix)) put(field.getLong(null), field.name) } catch (e: Exception) { Timber.w(e) } From 54e657547bff4925a74477998510db415af56de7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 02:04:18 -0400 Subject: [PATCH 021/285] v2.12.2 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index f74c4ac6..ae7065da 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 272 - versionName = "2.12.1" + versionCode = 273 + versionName = "2.12.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From 9c45c3c49e55412d0a78180f4892fc86cde31aea Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 14:09:42 +0800 Subject: [PATCH 022/285] Refine code style --- .../java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index b42d5e24..62db344d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -2,14 +2,12 @@ package be.mygod.vpnhotspot.manage import android.Manifest import android.content.* -import android.location.LocationManager import android.os.Build import android.os.IBinder import android.provider.Settings import android.view.View import android.widget.Toast import androidx.annotation.RequiresApi -import androidx.core.content.getSystemService import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.LocalOnlyHotspotService From 4b19dc37c218bf94254b2b5a21079e771fd7b06f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 02:13:51 -0400 Subject: [PATCH 023/285] Resume Google Play beta track --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f02531c8..3a6eaa96 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Connecting things to your VPN made simple. Share your VPN connection over hotspot or repeater. (**root required**) , -sign up for beta +sign up for beta at Google Play +or Firebase This app is useful for: From dfcc860adbcddc2b21642e1dbca043968521318e Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 01:25:07 -0400 Subject: [PATCH 024/285] Fix repeater stuck when Location is off on Android 11+ --- .../src/main/java/be/mygod/vpnhotspot/App.kt | 2 + .../be/mygod/vpnhotspot/RepeaterService.kt | 78 +++++++++++++------ .../manage/LocalOnlyHotspotManager.kt | 2 +- .../net/wifi/WifiP2pManagerHelper.kt | 11 ++- mobile/src/main/res/values-zh-rCN/strings.xml | 2 + mobile/src/main/res/values/strings.xml | 3 + 6 files changed, 73 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt index 9a17ac79..78b808f3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/App.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/App.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.Application import android.content.ClipboardManager import android.content.res.Configuration +import android.location.LocationManager import android.os.Build import android.util.Log import androidx.annotation.Size @@ -123,6 +124,7 @@ class App : Application() { } val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) } val clipboard by lazy { getSystemService()!! } + val location by lazy { getSystemService() } val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") } val customTabsIntent by lazy { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 21fed36f..b1456137 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -6,6 +6,7 @@ import android.app.Service import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.location.LocationManager import android.net.wifi.WpsInfo import android.net.wifi.p2p.* import android.os.Build @@ -20,7 +21,10 @@ import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestConnectionInfo import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestDeviceAddress +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestGroupInfo +import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestP2pState import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.requestPersistentGroupInfo import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps @@ -215,6 +219,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onP2pConnectionChanged( intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO), intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)) + LocationManager.MODE_CHANGED_ACTION -> @TargetApi(30) { + onLocationModeChanged(intent.getBooleanExtra(LocationManager.EXTRA_LOCATION_ENABLED, false)) + } } } private val deviceListener = broadcastReceiver { _, intent -> @@ -311,6 +318,30 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene } } + private var p2pPoller: Job? = null + @RequiresApi(30) + private fun onLocationModeChanged(enabled: Boolean) = if (enabled) p2pPoller?.cancel() else { + SmartSnackbar.make(R.string.repeater_location_off).apply { + action(R.string.repeater_location_off_configure) { + it.context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) + } + }.show() + p2pPoller = launch(start = CoroutineStart.UNDISPATCHED) { + while (true) { + delay(1000) + val channel = channel ?: return@launch + coroutineScope { + launch(start = CoroutineStart.UNDISPATCHED) { + if (p2pManager.requestP2pState(channel) == WifiP2pManager.WIFI_P2P_STATE_DISABLED) cleanLocked() + } + val info = async(start = CoroutineStart.UNDISPATCHED) { p2pManager.requestConnectionInfo(channel) } + val group = p2pManager.requestGroupInfo(channel) + onP2pConnectionChanged(info.await(), group) + } + } + } + } + /** * startService Step 1 */ @@ -321,29 +352,25 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene // bump self to foreground location service (API 29+) to use location later, also to avoid getting killed if (Build.VERSION.SDK_INT >= 26) showNotification() launch { - registerReceiver(receiver, intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, - WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) + val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, + WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) + if (Build.VERSION.SDK_INT >= 30) filter.addAction(LocationManager.MODE_CHANGED_ACTION) + registerReceiver(receiver, filter) receiverRegistered = true - try { - p2pManager.requestGroupInfo(channel) { - when { - it == null -> doStart() - it.isGroupOwner -> launch { if (routingManager == null) doStartLocked(it) } - else -> { - Timber.i("Removing old group ($it)") - p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { - override fun onSuccess() { - doStart() - } - override fun onFailure(reason: Int) = - startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason)) - }) + val group = p2pManager.requestGroupInfo(channel) + when { + group == null -> doStart() + group.isGroupOwner -> if (routingManager == null) doStartLocked(group) + else -> { + Timber.i("Removing old group ($group)") + p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { + override fun onSuccess() { + launch { doStart() } } - } + override fun onFailure(reason: Int) = + startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason)) + }) } - } catch (e: SecurityException) { - Timber.w(e) - startFailure(e.readableMessage) } } return START_NOT_STICKY @@ -351,15 +378,19 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene /** * startService Step 2 (if a group isn't already available) */ - private fun doStart() = launch { + private suspend fun doStart() { val listener = object : WifiP2pManager.ActionListener { override fun onFailure(reason: Int) { startFailure(formatReason(R.string.repeater_create_group_failure, reason), showWifiEnable = reason == WifiP2pManager.BUSY) } - override fun onSuccess() { } // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 + override fun onSuccess() { + // wait for WIFI_P2P_CONNECTION_CHANGED_ACTION to fire to go to step 3 + // in order for this to happen, we need to make sure that the callbacks are firing + if (Build.VERSION.SDK_INT >= 30) onLocationModeChanged(app.location?.isLocationEnabled == true) + } } - val channel = channel ?: return@launch listener.onFailure(WifiP2pManager.BUSY) + val channel = channel ?: return listener.onFailure(WifiP2pManager.BUSY) if (!safeMode) { binder.fetchPersistentGroup() setOperatingChannel() @@ -487,6 +518,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene private fun cleanLocked() { if (receiverRegistered) { ensureReceiverUnregistered(receiver) + p2pPoller?.cancel() receiverRegistered = false } if (Build.VERSION.SDK_INT >= 28) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index c2d7d51e..29f47448 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -66,7 +66,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager() if (if (Build.VERSION.SDK_INT < 28) @Suppress("DEPRECATION") { Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_OFF - } else context.getSystemService()?.isLocationEnabled != true) try { + } else app.location?.isLocationEnabled != true) try { context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show() } catch (e: ActivityNotFoundException) { 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 bbbc216e..1dd6bf2b 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 @@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.net.wifi import android.annotation.SuppressLint import android.net.wifi.WpsInfo import android.net.wifi.p2p.WifiP2pGroup +import android.net.wifi.p2p.WifiP2pInfo import android.net.wifi.p2p.WifiP2pManager import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.App.Companion.app @@ -129,7 +130,9 @@ object WifiP2pManagerHelper { return result.await() } - @SuppressLint("MissingPermission") + suspend fun WifiP2pManager.requestConnectionInfo(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestConnectionInfo(c) { complete(it) } }.await() + @SuppressLint("MissingPermission") // missing permission simply leads to null result @RequiresApi(29) suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? { val future = CompletableDeferred() @@ -139,4 +142,10 @@ object WifiP2pManagerHelper { if (address == MacAddressCompat.ANY_ADDRESS) null else address } } + @SuppressLint("MissingPermission") // missing permission simply leads to null result + suspend fun WifiP2pManager.requestGroupInfo(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestGroupInfo(c) { complete(it) } }.await() + @RequiresApi(29) + suspend fun WifiP2pManager.requestP2pState(c: WifiP2pManager.Channel) = + CompletableDeferred().apply { requestP2pState(c) { complete(it) } }.await() } diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 37095766..d3927cf8 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -29,6 +29,8 @@ 不支持此操作 服务不可用,请稍后重试 无线中继需要精确位置权限 + 由于系统限制,关闭位置信息服务可能产生问题并导致续航缩短 + 进入设置 临时 WLAN 热点 使用临时热点需要打开位置服务。 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index f3ce86b8..f2b020a9 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -47,6 +47,9 @@ Service unavailable. Try again later Repeater requires permissions for accessing fine location + Due to system restrictions, turning Location off may lead to things not working + properly and increased battery usage + Configure Temporary Wi\u2011Fi hotspot Temporary hotspot requires location to be turned on. From c0fdd5ef04d4d3578694727c0ce2a41a26ffe6c9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 14:09:42 +0800 Subject: [PATCH 025/285] Refine code style --- .../java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt index 29f47448..994d474a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotManager.kt @@ -3,13 +3,11 @@ package be.mygod.vpnhotspot.manage import android.Manifest import android.annotation.TargetApi import android.content.* -import android.location.LocationManager import android.os.Build import android.os.IBinder import android.provider.Settings import android.view.View import android.widget.Toast -import androidx.core.content.getSystemService import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.LocalOnlyHotspotService From aa1813687cec702159d46ca5aa0de515fdd7fd3e Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 14:42:36 +0800 Subject: [PATCH 026/285] v2.11.8 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 773ad12d..91c74d2e 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 260 - versionName = "2.11.7" + versionCode = 261 + versionName = "2.11.8" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From ffb7ec1017e1c29a5951ef17777da30d337d7e68 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 14:47:32 +0800 Subject: [PATCH 027/285] Fix missing import --- mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index b1456137..624b9870 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -12,6 +12,7 @@ import android.net.wifi.p2p.* import android.os.Build import android.os.Looper import android.provider.Settings +import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.core.content.edit import be.mygod.librootkotlinx.useParcel From 2fdb0ee909ee55b89d6eaddd4112461bcdc6e16b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 5 Jun 2021 15:07:51 -0400 Subject: [PATCH 028/285] Refine accessibility --- mobile/src/main/res/layout/fragment_ebeg.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/res/layout/fragment_ebeg.xml b/mobile/src/main/res/layout/fragment_ebeg.xml index deb5090a..1a632d07 100644 --- a/mobile/src/main/res/layout/fragment_ebeg.xml +++ b/mobile/src/main/res/layout/fragment_ebeg.xml @@ -65,6 +65,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" + android:minHeight="@dimen/touch_target_min" android:text="@string/donations__google_android_market_donate_button" /> From b887569cee31a959c229fe4cf12b254aa350a95a Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 8 Jun 2021 14:03:09 -0400 Subject: [PATCH 029/285] Fix more stream closed detections --- mobile/src/main/java/be/mygod/librootkotlinx/Utils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/Utils.kt b/mobile/src/main/java/be/mygod/librootkotlinx/Utils.kt index 290043d2..ee8c1a60 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/Utils.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/Utils.kt @@ -12,6 +12,7 @@ import android.util.* import androidx.annotation.RequiresApi import kotlinx.parcelize.Parcelize import java.io.IOException +import java.util.* class NoShellException(cause: Throwable) : Exception("Root missing", cause) @@ -252,4 +253,5 @@ inline fun ByteArray.toParcelable(classLoader: ClassLoa } // Stream closed caused in NullOutputStream -val IOException.isEBADF get() = message == "Stream closed" || (cause as? ErrnoException)?.errno == OsConstants.EBADF +val IOException.isEBADF get() = (cause as? ErrnoException)?.errno == OsConstants.EBADF || + message?.lowercase(Locale.ENGLISH) == "stream closed" From b402aed983bee62af3fcb6bb4c5238252173efd4 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 13:34:45 +0800 Subject: [PATCH 030/285] Update build tools --- mobile/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index ae7065da..2074899f 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -11,7 +11,7 @@ plugins { android { val javaVersion = JavaVersion.VERSION_1_8 val targetSdk = 29 - buildToolsVersion = "31.0.0-rc4" + buildToolsVersion = "31.0.0-rc5" compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = javaVersion From fac1451e95795a6c3605de43d95041216cdd0743 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 14:09:17 +0800 Subject: [PATCH 031/285] Fix pan initialization failure crash --- .../main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 83325254..5a52d2ab 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -103,7 +103,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : fun ensureInit(context: Context) { if (pan == null && BluetoothAdapter.getDefaultAdapter() != null) try { pan = pan(context, this) - } catch (e: InvocationTargetException) { + } catch (e: ReflectiveOperationException) { if (e.cause is SecurityException && BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) else Timber.w(e) activeFailureCause = e From 8918ff2377e5ed9ead8e68817a0500c81458fb3f Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 14:20:21 +0800 Subject: [PATCH 032/285] Fix new BluetoothPan constructor --- README.md | 2 ++ .../vpnhotspot/manage/BluetoothTethering.kt | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 99480ad5..879bbf03 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,8 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken) +* (since API 24, prior to API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V,unsupported` +* (since API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;Landroid/bluetooth/BluetoothAdapter;)V,unsupported` * (since API 24) `Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,sdk,system-api,test-api` * (since API 24) `Landroid/bluetooth/BluetoothProfile;->PAN:I,sdk,system-api,test-api` * (since API 30) `Landroid/content/Context;->TETHERING_SERVICE:Ljava/lang/String;,sdk,system-api,test-api` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 5a52d2ab..e76c04dc 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -27,14 +27,21 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private const val PAN = 5 private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") } private val constructor by lazy { - clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java).apply { + (if (BuildCompat.isAtLeastS()) { + clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java, + BluetoothAdapter::class.java) + } else { + clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java) + }).apply { isAccessible = true } } private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") } - fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener) = - constructor.newInstance(context, serviceListener) as BluetoothProfile + fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener, adapter: BluetoothAdapter) = + (if (BuildCompat.isAtLeastS()) { + constructor.newInstance(context, serviceListener, adapter) + } else constructor.newInstance(context, serviceListener)) as BluetoothProfile val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean fun BluetoothProfile.closePan() = BluetoothAdapter.getDefaultAdapter()!!.closeProfileProxy(PAN, this) @@ -101,8 +108,8 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val receiver = broadcastReceiver { _, _ -> stateListener() } fun ensureInit(context: Context) { - if (pan == null && BluetoothAdapter.getDefaultAdapter() != null) try { - pan = pan(context, this) + if (pan == null) try { + pan = pan(context, this, BluetoothAdapter.getDefaultAdapter() ?: return) } catch (e: ReflectiveOperationException) { if (e.cause is SecurityException && BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) else Timber.w(e) From 5d39ace3f04918ded826abd8c3f4452a23021cc5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 14:26:56 +0800 Subject: [PATCH 033/285] Use recommended API for getting bluetooth adapter --- .../vpnhotspot/manage/BluetoothTethering.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index e76c04dc..3e49495c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.manage import android.annotation.TargetApi import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.BroadcastReceiver import android.content.Context @@ -9,6 +10,7 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.TetheringManager @@ -38,12 +40,12 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : } private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") } - fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener, adapter: BluetoothAdapter) = - (if (BuildCompat.isAtLeastS()) { - constructor.newInstance(context, serviceListener, adapter) - } else constructor.newInstance(context, serviceListener)) as BluetoothProfile + private val adapter = app.getSystemService()?.adapter + fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener) = (if (BuildCompat.isAtLeastS()) { + constructor.newInstance(context, serviceListener, adapter) + } else constructor.newInstance(context, serviceListener)) as BluetoothProfile val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean - fun BluetoothProfile.closePan() = BluetoothAdapter.getDefaultAdapter()!!.closeProfileProxy(PAN, this) + fun BluetoothProfile.closePan() = adapter!!.closeProfileProxy(PAN, this) private fun registerBluetoothStateListener(receiver: BroadcastReceiver) = app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) @@ -72,7 +74,6 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : @RequiresApi(24) fun start(callback: TetheringManager.StartTetheringCallback) { if (pendingCallback != null) return - val adapter = BluetoothAdapter.getDefaultAdapter() try { if (adapter?.state == BluetoothAdapter.STATE_OFF) { registerBluetoothStateListener(this) @@ -95,7 +96,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : val pan = pan ?: return null if (!connected) return null activeFailureCause = null - return BluetoothAdapter.getDefaultAdapter()?.state == BluetoothAdapter.STATE_ON && try { + return adapter?.state == BluetoothAdapter.STATE_ON && try { pan.isTetheringOn } catch (e: InvocationTargetException) { activeFailureCause = e.cause ?: e @@ -109,7 +110,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : fun ensureInit(context: Context) { if (pan == null) try { - pan = pan(context, this, BluetoothAdapter.getDefaultAdapter() ?: return) + pan = pan(context, this) } catch (e: ReflectiveOperationException) { if (e.cause is SecurityException && BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) else Timber.w(e) From edca8ed0134144b8885060e8e0aa527b40e87201 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 14:59:09 +0800 Subject: [PATCH 034/285] Rely on Android implicitly granting BLUETOOTH_CONNECT --- mobile/src/main/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index e4fa8a08..69d08080 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -52,8 +52,10 @@ - + + Date: Fri, 11 Jun 2021 01:34:53 +0800 Subject: [PATCH 035/285] Clear error when permission is granted --- .../java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt | 1 + .../main/java/be/mygod/vpnhotspot/manage/TetherManager.kt | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 3e49495c..cedc9c0a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -109,6 +109,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val receiver = broadcastReceiver { _, _ -> stateListener() } fun ensureInit(context: Context) { + activeFailureCause = null if (pan == null) try { pan = pan(context, this) } catch (e: ReflectiveOperationException) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index f2f9fa67..55dd363f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -297,11 +297,14 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), parent.viewLifecycleOwner.lifecycle.addObserver(this) } - fun ensureInit(context: Context) = tethering.ensureInit(context) + fun ensureInit(context: Context) { + tethering.ensureInit(context) + onTetheringStarted() // force flush + } override fun onResume(owner: LifecycleOwner) { if (!BuildCompat.isAtLeastS() || parent.requireContext().checkSelfPermission( Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { - ensureInit(parent.requireContext()) + tethering.ensureInit(parent.requireContext()) } else if (parent.shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) { parent.requestBluetooth.launch(Manifest.permission.BLUETOOTH_CONNECT) } From 54c9ae0ceccffd82490b4ad970369b4813c874be Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 01:46:51 +0800 Subject: [PATCH 036/285] Fix turning off bluetooth tethering --- .../vpnhotspot/manage/BluetoothTethering.kt | 48 ++++++++++++------- .../mygod/vpnhotspot/manage/TetherManager.kt | 5 +- .../vpnhotspot/manage/TetheringTileService.kt | 5 +- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index cedc9c0a..4f79aa44 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -67,27 +67,11 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : pendingCallback = null app.unregisterReceiver(this) } - - /** - * https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384 - */ - @RequiresApi(24) - fun start(callback: TetheringManager.StartTetheringCallback) { - if (pendingCallback != null) return - try { - if (adapter?.state == BluetoothAdapter.STATE_OFF) { - registerBluetoothStateListener(this) - pendingCallback = callback - adapter.enable() - } else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) - } catch (e: SecurityException) { - SmartSnackbar.make(e.readableMessage).shortToast().show() - } - } } private var connected = false private var pan: BluetoothProfile? = null + private var stoppedByUser = false var activeFailureCause: Throwable? = null /** * Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java @@ -96,7 +80,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : val pan = pan ?: return null if (!connected) return null activeFailureCause = null - return adapter?.state == BluetoothAdapter.STATE_ON && try { + val on = adapter?.state == BluetoothAdapter.STATE_ON && try { pan.isTetheringOn } catch (e: InvocationTargetException) { activeFailureCause = e.cause ?: e @@ -104,6 +88,10 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : else Timber.w(e) return null } + return if (stoppedByUser) { + if (!on) stoppedByUser = false + false + } else on } private val receiver = broadcastReceiver { _, _ -> stateListener() } @@ -125,11 +113,35 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : override fun onServiceDisconnected(profile: Int) { connected = false + stoppedByUser = false } override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { connected = true stateListener() } + + /** + * https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384 + */ + @RequiresApi(24) + fun start(callback: TetheringManager.StartTetheringCallback) { + if (pendingCallback != null) return + try { + if (adapter?.state == BluetoothAdapter.STATE_OFF) { + registerBluetoothStateListener(BluetoothTethering) + pendingCallback = callback + adapter.enable() + } else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) + } catch (e: SecurityException) { + SmartSnackbar.make(e.readableMessage).shortToast().show() + } + } + @RequiresApi(24) + fun stop(callback: (Exception) -> Unit) { + TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, callback) + stoppedByUser = true + } + override fun close() { app.unregisterReceiver(receiver) pan?.closePan() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 55dd363f..ac9981ec 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -319,10 +319,9 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), if (tethering.active == null) tethering.activeFailureCause?.readableMessage else null, baseError).joinToString("\n") - override fun start() = BluetoothTethering.start(this) + override fun start() = tethering.start(this) override fun stop() { - TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException) - Thread.sleep(1) // give others a room to breathe + tethering.stop(this::onException) onTetheringStarted() // force flush state } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index 6483beea..ed0c74be 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -151,10 +151,9 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin override val labelString get() = R.string.tethering_manage_bluetooth override val tetherType get() = TetherType.BLUETOOTH - override fun start() = BluetoothTethering.start(this) + override fun start() = tethering!!.start(this) override fun stop() { - TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException) - Thread.sleep(1) // give others a room to breathe + tethering!!.stop(this::onException) onTetheringStarted() // force flush state } From 6f81f8a6fff95a2bd2475427da7f3d8881ccdadc Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 01:54:22 +0800 Subject: [PATCH 037/285] Reset pendingCallback on failure --- .../java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 4f79aa44..d20085be 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -125,8 +125,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : */ @RequiresApi(24) fun start(callback: TetheringManager.StartTetheringCallback) { - if (pendingCallback != null) return - try { + if (pendingCallback == null) try { if (adapter?.state == BluetoothAdapter.STATE_OFF) { registerBluetoothStateListener(BluetoothTethering) pendingCallback = callback @@ -134,6 +133,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : } else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) } catch (e: SecurityException) { SmartSnackbar.make(e.readableMessage).shortToast().show() + pendingCallback = null } } @RequiresApi(24) From 440b22faa19246ca4ab86345ea95d4d1fbc8d10a Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 02:15:49 +0800 Subject: [PATCH 038/285] Properly handle missing bluetooth permissions --- .../vpnhotspot/manage/BluetoothTethering.kt | 2 ++ .../mygod/vpnhotspot/manage/TetherManager.kt | 24 ++++++++++++------- .../vpnhotspot/manage/TetheringTileService.kt | 7 +++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index d20085be..de626edb 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -1,5 +1,6 @@ package be.mygod.vpnhotspot.manage +import android.annotation.SuppressLint import android.annotation.TargetApi import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager @@ -123,6 +124,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : /** * https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384 */ + @SuppressLint("MissingPermission") @RequiresApi(24) fun start(callback: TetheringManager.StartTetheringCallback) { if (pendingCallback == null) try { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index ac9981ec..86290ca2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -62,12 +62,16 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), } catch (e: RuntimeException) { app.logEvent("manage_write_settings") { param("message", e.toString()) } } - if (manager.isStarted) try { - manager.stop() - } catch (e: InvocationTargetException) { - if (e.targetException !is SecurityException) Timber.w(e) - manager.onException(e) - } else manager.start() + when (manager.isStarted) { + true -> try { + manager.stop() + } catch (e: InvocationTargetException) { + if (e.targetException !is SecurityException) Timber.w(e) + manager.onException(e) + } + false -> manager.start() + null -> manager.onClickNull() + } } } @@ -78,13 +82,13 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), override val icon get() = tetherType.icon override val title get() = this@TetherManager.title override val text get() = this@TetherManager.text - override val active get() = isStarted + override val active get() = isStarted == true } val data = Data() abstract val title: CharSequence abstract val tetherType: TetherType - open val isStarted get() = parent.enabledTypes.contains(tetherType) + open val isStarted: Boolean? get() = parent.enabledTypes.contains(tetherType) protected open val text: CharSequence get() = baseError ?: "" protected var baseError: String? = null @@ -92,6 +96,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), protected abstract fun start() protected abstract fun stop() + protected open fun onClickNull(): Unit = throw UnsupportedOperationException() override fun onTetheringStarted() = data.notifyChange() override fun onTetheringFailed(error: Int?) { @@ -314,7 +319,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), override val title get() = parent.getString(R.string.tethering_manage_bluetooth) override val tetherType get() = TetherType.BLUETOOTH override val type get() = VIEW_TYPE_BLUETOOTH - override val isStarted get() = tethering.active == true + override val isStarted get() = tethering.active override val text get() = listOfNotNull( if (tethering.active == null) tethering.activeFailureCause?.readableMessage else null, baseError).joinToString("\n") @@ -324,6 +329,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), tethering.stop(this::onException) onTetheringStarted() // force flush state } + override fun onClickNull() = ManageBar.start(parent.requireContext()) } @RequiresApi(30) class Ethernet(parent: TetheringFragment) : TetherManager(parent) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index ed0c74be..4efd8174 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -186,7 +186,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin icon = tileOff } null -> { - state = Tile.STATE_UNAVAILABLE + state = Tile.STATE_INACTIVE icon = tileOff subtitle(tethering?.activeFailureCause?.readableMessage) } @@ -197,7 +197,8 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin } override fun onClick() { - when (tethering?.active) { + val tethering = tethering + if (tethering == null) tapPending = true else when (tethering.active) { true -> { val binder = binder if (binder == null) tapPending = true else { @@ -211,7 +212,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin } } false -> start() - else -> tapPending = true + else -> ManageBar.start(this) } } } From 45638decb1926e65da77fe20af92058c2e3cf72f Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 16:24:32 -0400 Subject: [PATCH 039/285] Mark remaining tri-state TileServices as toggleable as well --- mobile/src/main/AndroidManifest.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 69d08080..0dc395d0 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -143,6 +143,9 @@ + + + + + + + Date: Thu, 10 Jun 2021 16:41:11 -0400 Subject: [PATCH 040/285] v2.12.3 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 2074899f..14fdaf88 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 273 - versionName = "2.12.2" + versionCode = 274 + versionName = "2.12.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From d87983274e161916123d81a12e03bd82a73dc938 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 10 Jun 2021 22:50:26 -0400 Subject: [PATCH 041/285] Fix permission request --- .../java/be/mygod/vpnhotspot/manage/RepeaterManager.kt | 7 +------ .../java/be/mygod/vpnhotspot/manage/TetheringFragment.kt | 6 +++++- 2 files changed, 6 insertions(+), 7 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 137fd2b1..796c8282 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -5,7 +5,6 @@ import android.content.ComponentName import android.content.DialogInterface import android.content.Intent import android.content.ServiceConnection -import android.content.pm.PackageManager import android.net.wifi.SoftApConfiguration import android.net.wifi.p2p.WifiP2pGroup import android.os.Build @@ -90,11 +89,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic when (binder?.service?.status) { RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context -> ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java)) - } else if (parent.requireContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == - PackageManager.PERMISSION_GRANTED || - parent.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) { - parent.startRepeater.launch(Manifest.permission.ACCESS_FINE_LOCATION) - } else SmartSnackbar.make(R.string.repeater_missing_location_permissions).shortToast().show() + } else parent.startRepeater.launch(Manifest.permission.ACCESS_FINE_LOCATION) RepeaterService.Status.ACTIVE -> binder.shutdown() else -> { } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index 25be6bb0..ba2e52af 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -34,6 +34,7 @@ import be.mygod.vpnhotspot.root.RootManager import be.mygod.vpnhotspot.root.WifiApCommands import be.mygod.vpnhotspot.util.* import be.mygod.vpnhotspot.widget.SmartSnackbar +import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import timber.log.Timber @@ -130,7 +131,10 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick @RequiresApi(29) val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - if (granted) requireActivity().startForegroundService(Intent(activity, RepeaterService::class.java)) + if (granted) requireActivity().startForegroundService(Intent(activity, RepeaterService::class.java)) else { + Snackbar.make((activity as MainActivity).binding.fragmentHolder, + R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show() + } } @RequiresApi(26) val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) { From 92ed445fc04e30b44da101344933db17d3d52857 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 00:24:48 -0400 Subject: [PATCH 042/285] Properly locate BluetoothPan constructors --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 879bbf03..ead0ce40 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ API restrictions are updated up to [commit `ebe7044`](https://android.googlesour Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used) +* (since API 24, prior to API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V,unsupported` +* (since API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;Landroid/bluetooth/BluetoothAdapter;)V,unsupported` * (prior to API 30) `Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,max-target-r` * (since API 30) `Landroid/net/ConnectivityModuleConnector;->IN_PROCESS_SUFFIX:Ljava/lang/String;` * (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfaceRegexpsChanged(Landroid/net/TetheringManager$TetheringInterfaceRegexps;)V,blocked` @@ -192,8 +194,6 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded
Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken) -* (since API 24, prior to API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V,unsupported` -* (since API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;Landroid/bluetooth/BluetoothAdapter;)V,unsupported` * (since API 24) `Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,sdk,system-api,test-api` * (since API 24) `Landroid/bluetooth/BluetoothProfile;->PAN:I,sdk,system-api,test-api` * (since API 30) `Landroid/content/Context;->TETHERING_SERVICE:Ljava/lang/String;,sdk,system-api,test-api` From d17bb74e7b443d650c0fd854ac3a9c45eae05ab8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 01:08:34 -0400 Subject: [PATCH 043/285] Stop using unsupported API for bluetooth --- README.md | 2 -- .../vpnhotspot/manage/BluetoothTethering.kt | 31 ++++++------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ead0ce40..99480ad5 100644 --- a/README.md +++ b/README.md @@ -151,8 +151,6 @@ API restrictions are updated up to [commit `ebe7044`](https://android.googlesour Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used) -* (since API 24, prior to API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;)V,unsupported` -* (since API 31) `Landroid/bluetooth/BluetoothPan;->(Landroid/content/Context;Landroid/bluetooth/BluetoothProfile$ServiceListener;Landroid/bluetooth/BluetoothAdapter;)V,unsupported` * (prior to API 30) `Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,max-target-r` * (since API 30) `Landroid/net/ConnectivityModuleConnector;->IN_PROCESS_SUFFIX:Ljava/lang/String;` * (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfaceRegexpsChanged(Landroid/net/TetheringManager$TetheringInterfaceRegexps;)V,blocked` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index de626edb..1dce98e2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -29,24 +29,10 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : */ private const val PAN = 5 private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") } - private val constructor by lazy { - (if (BuildCompat.isAtLeastS()) { - clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java, - BluetoothAdapter::class.java) - } else { - clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java) - }).apply { - isAccessible = true - } - } private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") } private val adapter = app.getSystemService()?.adapter - fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener) = (if (BuildCompat.isAtLeastS()) { - constructor.newInstance(context, serviceListener, adapter) - } else constructor.newInstance(context, serviceListener)) as BluetoothProfile - val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean - fun BluetoothProfile.closePan() = adapter!!.closeProfileProxy(PAN, this) + private val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean private fun registerBluetoothStateListener(receiver: BroadcastReceiver) = app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) @@ -70,6 +56,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : } } + private var proxyCreated = false private var connected = false private var pan: BluetoothProfile? = null private var stoppedByUser = false @@ -98,12 +85,13 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val receiver = broadcastReceiver { _, _ -> stateListener() } fun ensureInit(context: Context) { + val adapter = adapter ?: return activeFailureCause = null - if (pan == null) try { - pan = pan(context, this) - } catch (e: ReflectiveOperationException) { - if (e.cause is SecurityException && BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) - else Timber.w(e) + if (!proxyCreated) try { + check(adapter.getProfileProxy(context, this, PAN)) + proxyCreated = true + } catch (e: SecurityException) { + if (BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) else Timber.w(e) activeFailureCause = e } } @@ -117,6 +105,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : stoppedByUser = false } override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { + pan = proxy connected = true stateListener() } @@ -146,6 +135,6 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : override fun close() { app.unregisterReceiver(receiver) - pan?.closePan() + adapter!!.closeProfileProxy(PAN, pan) } } From cc5cdec0c51591d56bffa732d3f2b5e002ee2d14 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 01:13:18 -0400 Subject: [PATCH 044/285] Fix updating README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 99480ad5..ac7f4fcb 100644 --- a/README.md +++ b/README.md @@ -183,8 +183,8 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Lcom/android/server/wifi/WifiContext;->ACTION_RESOURCES_APK:Ljava/lang/String;` * (since API 29) `Lcom/android/server/wifi/p2p/WifiP2pServiceImpl;->ANONYMIZED_DEVICE_ADDRESS:Ljava/lang/String;` * (since API 30) `Lcom/android/server/SystemServer;->TETHERING_CONNECTOR_CLASS:Ljava/lang/String;` -* (since API 29) `Ldalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;,core-platform-api,greylist` -* (since API 29) `Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V,blacklist,core-platform-api` +* (since API 29) `Ldalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;,core-platform-api,unsupported` +* (since API 29) `Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V,blocked,core-platform-api` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->(Ljava/lang/Class;I)V,unsupported` * (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o` * (prior to API 29) `Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,max-target-p` @@ -284,8 +284,8 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (on API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_INVALID:I,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/SoftApInfo;->getAutoShutdownTimeoutMillis()J,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/SoftApInfo;->getBssid()Landroid/net/MacAddress;,sdk,system-api,test-api` -* (since API 30) `Landroid/net/wifi/SoftApInfo;->getBandwidth()I,system-api,whitelist` -* (since API 30) `Landroid/net/wifi/SoftApInfo;->getFrequency()I,system-api,whitelist` +* (since API 30) `Landroid/net/wifi/SoftApInfo;->getBandwidth()I,sdk,system-api,test-api` +* (since API 30) `Landroid/net/wifi/SoftApInfo;->getFrequency()I,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/SoftApInfo;->getWifiStandard()I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiClient;->getMacAddress()Landroid/net/MacAddress;,sdk,system-api,test-api` * (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA2_PSK:I,sdk,system-api,test-api` From bb80359efbd704a0a4c13011b313c3620fe8c32e Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 02:20:55 -0400 Subject: [PATCH 045/285] Refactor LOHService based on AOSP CarProjectionService --- README.md | 10 ++- .../vpnhotspot/LocalOnlyHotspotService.kt | 68 +++++++-------- .../mygod/vpnhotspot/manage/TetherManager.kt | 4 +- .../mygod/vpnhotspot/net/TetheringManager.kt | 8 +- .../vpnhotspot/net/wifi/WifiApManager.kt | 85 ++++++++++++++++++- 5 files changed, 128 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index ac7f4fcb..221c9d18 100644 --- a/README.md +++ b/README.md @@ -295,9 +295,17 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (on API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Landroid/net/wifi/SoftApInfo;)V,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Ljava/util/List;)V,sdk,system-api,test-api` * (since API 28) `Landroid/net/wifi/WifiManager$SoftApCallback;->onStateChanged(II)V,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_FAILURE_REASON:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_INTERFACE_NAME:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->SAP_CLIENT_BLOCK_REASON_CODE_*:I,sdk,system-api,test-api` * (since API 28) `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api` -* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api` +* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->getSoftApConfiguration()Landroid/net/wifi/SoftApConfiguration;,sdk,system-api,test-api` * (prior to API 30) `Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->isApMacRandomizationSupported()Z,sdk,system-api,test-api` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 7c80c71b..1055f2f8 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -6,16 +6,12 @@ import android.net.wifi.WifiManager import android.os.Build import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.net.IpNeighbour -import be.mygod.vpnhotspot.net.TetherType -import be.mygod.vpnhotspot.net.TetheringManager -import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat import be.mygod.vpnhotspot.net.wifi.WifiApManager import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.StickyEvent1 -import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.widget.SmartSnackbar import kotlinx.coroutines.* import timber.log.Timber @@ -43,7 +39,8 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { null -> return // stopped "" -> WifiApManager.cancelLocalOnlyHotspotRequest() } - reservation?.close() ?: stopService() + reservation?.close() + stopService() } } @@ -56,24 +53,6 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { override val coroutineContext = dispatcher + Job() private var routingManager: RoutingManager? = null private var timeoutMonitor: TetherTimeoutMonitor? = null - private var receiverRegistered = false - private val receiver = broadcastReceiver { _, intent -> - val ifaces = (intent.localOnlyTetheredIfaces ?: return@broadcastReceiver).filter { - TetherType.ofInterface(it) != TetherType.WIFI_P2P - } - Timber.d("onTetherStateChangedLocked: $ifaces") - check(ifaces.size <= 1) - val iface = ifaces.singleOrNull() - binder.iface = iface - if (iface.isNullOrEmpty()) stopService() else launch { - val routingManager = routingManager - if (routingManager == null) { - this@LocalOnlyHotspotService.routingManager = RoutingManager.LocalOnly(this@LocalOnlyHotspotService, - iface).apply { start() } - IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService) - } else check(iface == routingManager.downstream) - } - } override val activeIfaces get() = binder.iface.let { if (it.isNullOrEmpty()) emptyList() else listOf(it) } override fun onBind(intent: Intent?) = binder @@ -87,20 +66,37 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) { if (reservation == null) onFailed(-2) else { this@LocalOnlyHotspotService.reservation = reservation - if (!receiverRegistered) { - val configuration = binder.configuration!! - if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) { - timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis, - coroutineContext) { reservation.close() } + val configuration = binder.configuration!! + if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) { + timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis, + coroutineContext) { reservation.close() } + } + // based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160 + val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!! + val apState = sticky.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, + WifiApManager.WIFI_AP_STATE_DISABLED) + val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME) + if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) { + if (apState == WifiApManager.WIFI_AP_STATE_FAILED) { + SmartSnackbar.make(getString(R.string.tethering_temp_hotspot_failure, + WifiApManager.failureReasonLookup(sticky.getIntExtra( + WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0)))).show() } - registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) - receiverRegistered = true + return stopService() + } + binder.iface = iface + launch { + check(routingManager == null) + routingManager = RoutingManager.LocalOnly( + this@LocalOnlyHotspotService, iface).apply { start() } + IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService) } } } override fun onStopped() { Timber.d("LOHCallback.onStopped") + reservation?.close() reservation = null } @@ -152,14 +148,10 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { } private fun unregisterReceiver(exit: Boolean = false) { - if (receiverRegistered) { - unregisterReceiver(receiver) - IpNeighbourMonitor.unregisterCallback(this) - if (Build.VERSION.SDK_INT >= 28) { - timeoutMonitor?.close() - timeoutMonitor = null - } - receiverRegistered = false + IpNeighbourMonitor.unregisterCallback(this) + if (Build.VERSION.SDK_INT >= 28) { + timeoutMonitor?.close() + timeoutMonitor = null } launch { routingManager?.stop() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 86290ca2..2e039e13 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -160,11 +160,11 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), } override fun onStateChanged(state: Int, failureReason: Int) { - if (state < 10 || state > 14) { + if (state < WifiApManager.WIFI_AP_STATE_DISABLING || state > WifiApManager.WIFI_AP_STATE_FAILED) { Timber.w(Exception("Unknown state $state, $failureReason")) return } - this.failureReason = if (state == 14) failureReason else null // WIFI_AP_STATE_FAILED + this.failureReason = if (state == WifiApManager.WIFI_AP_STATE_FAILED) failureReason else null data.notifyChange() } override fun onNumClientsChanged(numClients: Int) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index c5f0a250..56ac2303 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -134,7 +134,7 @@ object TetheringManager { * Requires MANAGE_USB permission, unfortunately. * * Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389 - * @see [startTethering]. + * @see startTethering */ @RequiresApi(24) const val TETHERING_USB = 1 @@ -142,14 +142,14 @@ object TetheringManager { * Bluetooth tethering type. * * Requires BLUETOOTH permission. - * @see [startTethering]. + * @see startTethering */ @RequiresApi(24) const val TETHERING_BLUETOOTH = 2 /** * Ncm local tethering type. * - * @see [startTethering] + * @see startTethering */ @RequiresApi(30) const val TETHERING_NCM = 4 @@ -157,7 +157,7 @@ object TetheringManager { * Ethernet tethering type. * * Requires MANAGE_USB permission, also. - * @see [startTethering] + * @see startTethering */ @RequiresApi(30) const val TETHERING_ETHERNET = 5 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index b3ef3aa2..811f9d2c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -59,6 +59,87 @@ object WifiApManager { @get:RequiresApi(30) val isApMacRandomizationSupported get() = apMacRandomizationSupported(Services.wifi) as Boolean + /** + * Broadcast intent action indicating that Wi-Fi AP has been enabled, disabled, + * enabling, disabling, or failed. + */ + const val WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED" + /** + * The lookup key for an int that indicates whether Wi-Fi AP is enabled, + * disabled, enabling, disabling, or failed. Retrieve it with [Intent.getIntExtra]. + * + * @see WIFI_AP_STATE_DISABLED + * @see WIFI_AP_STATE_DISABLING + * @see WIFI_AP_STATE_ENABLED + * @see WIFI_AP_STATE_ENABLING + * @see WIFI_AP_STATE_FAILED + */ + const val EXTRA_WIFI_AP_STATE = "wifi_state" + /** + * An extra containing the int error code for Soft AP start failure. + * Can be obtained from the [WIFI_AP_STATE_CHANGED_ACTION] using [Intent.getIntExtra]. + * This extra will only be attached if [EXTRA_WIFI_AP_STATE] is + * attached and is equal to [WIFI_AP_STATE_FAILED]. + * + * The error code will be one of: + * {@link #SAP_START_FAILURE_GENERAL}, + * {@link #SAP_START_FAILURE_NO_CHANNEL}, + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION} + * + * Source: https://android.googlesource.com/platform/frameworks/base/+/android-6.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#210 + */ + @get:RequiresApi(23) + val EXTRA_WIFI_AP_FAILURE_REASON get() = + if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_FAILURE_REASON" else "wifi_ap_error_code" + /** + * The lookup key for a String extra that stores the interface name used for the Soft AP. + * This extra is included in the broadcast [WIFI_AP_STATE_CHANGED_ACTION]. + * Retrieve its value with [Intent.getStringExtra]. + * + * Source: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r1/wifi/java/android/net/wifi/WifiManager.java#413 + */ + @get:RequiresApi(26) + val EXTRA_WIFI_AP_INTERFACE_NAME get() = + if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME" else "wifi_ap_interface_name" + /** + * Wi-Fi AP is currently being disabled. The state will change to + * [WIFI_AP_STATE_DISABLED] if it finishes successfully. + * + * @see WIFI_AP_STATE_CHANGED_ACTION + * @see #getWifiApState() + */ + const val WIFI_AP_STATE_DISABLING = 10 + /** + * Wi-Fi AP is disabled. + * + * @see WIFI_AP_STATE_CHANGED_ACTION + * @see #getWifiState() + */ + const val WIFI_AP_STATE_DISABLED = 11 + /** + * Wi-Fi AP is currently being enabled. The state will change to + * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully. + * + * @see WIFI_AP_STATE_CHANGED_ACTION + * @see #getWifiApState() + */ + const val WIFI_AP_STATE_ENABLING = 12 + /** + * Wi-Fi AP is enabled. + * + * @see WIFI_AP_STATE_CHANGED_ACTION + * @see #getWifiApState() + */ + const val WIFI_AP_STATE_ENABLED = 13 + /** + * Wi-Fi AP is in a failed state. This state will occur when an error occurs during + * enabling or disabling + * + * @see WIFI_AP_STATE_CHANGED_ACTION + * @see #getWifiApState() + */ + const val WIFI_AP_STATE_FAILED = 14 + private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") } @Suppress("DEPRECATION") private val setWifiApConfiguration by lazy { @@ -88,7 +169,7 @@ object WifiApManager { /** * Called when soft AP state changes. * - * @param state the new AP state. One of {@link #WIFI_AP_STATE_DISABLED}, + * @param state the new AP state. One of [WIFI_AP_STATE_DISABLED], * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} * @param failureReason reason when in failed state. One of @@ -146,7 +227,7 @@ object WifiApManager { @RequiresApi(30) fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) { } } - @RequiresApi(28) + @RequiresApi(23) val failureReasonLookup = ConstantLookup("SAP_START_FAILURE_", "GENERAL", "NO_CHANNEL") @get:RequiresApi(30) val clientBlockLookup by lazy { ConstantLookup("SAP_CLIENT_") } From 2bde3330b6af7cc43bfac8d557d1f7d9448659c4 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 02:35:11 -0400 Subject: [PATCH 046/285] Support ap error codes on Android 6-8.1 --- README.md | 16 +++++++------- .../vpnhotspot/LocalOnlyHotspotService.kt | 3 +-- .../mygod/vpnhotspot/manage/TetherManager.kt | 21 ++++++++++++++----- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 221c9d18..f9e95913 100644 --- a/README.md +++ b/README.md @@ -295,17 +295,17 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (on API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Landroid/net/wifi/SoftApInfo;)V,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Ljava/util/List;)V,sdk,system-api,test-api` * (since API 28) `Landroid/net/wifi/WifiManager$SoftApCallback;->onStateChanged(II)V,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_FAILURE_REASON:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_FAILURE_REASON:Ljava/lang/String;,sdk,system-api,test-api` * (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_INTERFACE_NAME:Ljava/lang/String;,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->EXTRA_WIFI_AP_STATE:Ljava/lang/String;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->SAP_CLIENT_BLOCK_REASON_CODE_*:I,sdk,system-api,test-api` -* (since API 28) `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api` +* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api` +* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api` * (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api` +* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->getSoftApConfiguration()Landroid/net/wifi/SoftApConfiguration;,sdk,system-api,test-api` * (prior to API 30) `Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->isApMacRandomizationSupported()Z,sdk,system-api,test-api` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 1055f2f8..ee6d3352 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -73,8 +73,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { } // based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160 val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!! - val apState = sticky.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, - WifiApManager.WIFI_AP_STATE_DISABLED) + val apState = sticky.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, 0) val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME) if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) { if (apState == WifiApManager.WIFI_AP_STATE_FAILED) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 2e039e13..738c188a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -4,6 +4,7 @@ import android.Manifest import android.annotation.TargetApi import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.pm.PackageManager import android.os.Build import android.os.Parcelable @@ -141,22 +142,32 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), @RequiresApi(24) class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver, WifiApManager.SoftApCallbackCompat { + private val receiver = broadcastReceiver { _, intent -> + failureReason = if (intent.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, 0) == + WifiApManager.WIFI_AP_STATE_FAILED) { + intent.getIntExtra(WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0) + } else null + data.notifyChange() + } private var failureReason: Int? = null private var numClients: Int? = null private var info = emptyList() private var capability: Parcelable? = null init { - if (Build.VERSION.SDK_INT >= 28) parent.viewLifecycleOwner.lifecycle.addObserver(this) + if (Build.VERSION.SDK_INT >= 23) parent.viewLifecycleOwner.lifecycle.addObserver(this) } - @TargetApi(28) override fun onStart(owner: LifecycleOwner) { - WifiApCommands.registerSoftApCallback(this) + if (Build.VERSION.SDK_INT < 28) { + parent.requireContext().registerReceiver(receiver, + IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION)) + } else WifiApCommands.registerSoftApCallback(this) } - @TargetApi(28) override fun onStop(owner: LifecycleOwner) { - WifiApCommands.unregisterSoftApCallback(this) + if (Build.VERSION.SDK_INT < 28) { + parent.requireContext().unregisterReceiver(receiver) + } else WifiApCommands.unregisterSoftApCallback(this) } override fun onStateChanged(state: Int, failureReason: Int) { From e9979495e48b62e6a39f5f0544417ce4fd446b32 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 02:41:29 -0400 Subject: [PATCH 047/285] Check wifi ap state everywhere --- README.md | 8 ++++---- .../java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt | 3 ++- .../java/be/mygod/vpnhotspot/manage/TetherManager.kt | 9 +++------ .../java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt | 9 ++++++++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f9e95913..0c80287a 100644 --- a/README.md +++ b/README.md @@ -301,10 +301,10 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Landroid/net/wifi/WifiManager;->SAP_CLIENT_BLOCK_REASON_CODE_*:I,sdk,system-api,test-api` * (since API 23) `Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,sdk,system-api,test-api` * (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_CHANGED_ACTION:Ljava/lang/String;,sdk,system-api,test-api` -* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api` -* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api` -* (since API 26) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api` -* (since API 28) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLED:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_DISABLING:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLED:I,sdk,system-api,test-api` +* (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_ENABLING:I,sdk,system-api,test-api` * (since API 23) `Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiManager;->getSoftApConfiguration()Landroid/net/wifi/SoftApConfiguration;,sdk,system-api,test-api` * (prior to API 30) `Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,sdk,system-api,test-api` diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index ee6d3352..95ead806 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -10,6 +10,7 @@ import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat import be.mygod.vpnhotspot.net.wifi.WifiApManager +import be.mygod.vpnhotspot.net.wifi.WifiApManager.wifiApState import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.StickyEvent1 import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -73,7 +74,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { } // based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160 val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!! - val apState = sticky.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, 0) + val apState = sticky.wifiApState val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME) if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) { if (apState == WifiApManager.WIFI_AP_STATE_FAILED) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 738c188a..93caf1c3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -27,6 +27,7 @@ import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.wifi.* +import be.mygod.vpnhotspot.net.wifi.WifiApManager.wifiApState import be.mygod.vpnhotspot.root.WifiApCommands import be.mygod.vpnhotspot.util.* import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -143,8 +144,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver, WifiApManager.SoftApCallbackCompat { private val receiver = broadcastReceiver { _, intent -> - failureReason = if (intent.getIntExtra(WifiApManager.EXTRA_WIFI_AP_STATE, 0) == - WifiApManager.WIFI_AP_STATE_FAILED) { + failureReason = if (intent.wifiApState == WifiApManager.WIFI_AP_STATE_FAILED) { intent.getIntExtra(WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0) } else null data.notifyChange() @@ -171,10 +171,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), } override fun onStateChanged(state: Int, failureReason: Int) { - if (state < WifiApManager.WIFI_AP_STATE_DISABLING || state > WifiApManager.WIFI_AP_STATE_FAILED) { - Timber.w(Exception("Unknown state $state, $failureReason")) - return - } + if (!WifiApManager.checkWifiApState(state)) return this.failureReason = if (state == WifiApManager.WIFI_AP_STATE_FAILED) failureReason else null data.notifyChange() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 811f9d2c..03e86367 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -74,7 +74,7 @@ object WifiApManager { * @see WIFI_AP_STATE_ENABLING * @see WIFI_AP_STATE_FAILED */ - const val EXTRA_WIFI_AP_STATE = "wifi_state" + private const val EXTRA_WIFI_AP_STATE = "wifi_state" /** * An extra containing the int error code for Soft AP start failure. * Can be obtained from the [WIFI_AP_STATE_CHANGED_ACTION] using [Intent.getIntExtra]. @@ -101,6 +101,13 @@ object WifiApManager { @get:RequiresApi(26) val EXTRA_WIFI_AP_INTERFACE_NAME get() = if (Build.VERSION.SDK_INT >= 30) "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME" else "wifi_ap_interface_name" + + fun checkWifiApState(state: Int) = if (state < WIFI_AP_STATE_DISABLING || state > WIFI_AP_STATE_FAILED) { + Timber.w(Exception("Unknown state $state")) + false + } else true + val Intent.wifiApState get() = + getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED).also { checkWifiApState(it) } /** * Wi-Fi AP is currently being disabled. The state will change to * [WIFI_AP_STATE_DISABLED] if it finishes successfully. From 0c0ac98e43103f3a91bf16e47181cf3b14457d35 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 03:00:07 -0400 Subject: [PATCH 048/285] Fix comment links --- .../main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 03e86367..9c4cbd37 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -176,9 +176,8 @@ object WifiApManager { /** * Called when soft AP state changes. * - * @param state the new AP state. One of [WIFI_AP_STATE_DISABLED], - * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, - * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} + * @param state the new AP state. One of [WIFI_AP_STATE_DISABLED], [WIFI_AP_STATE_DISABLING], + * [WIFI_AP_STATE_ENABLED], [WIFI_AP_STATE_ENABLING], [WIFI_AP_STATE_FAILED] * @param failureReason reason when in failed state. One of * {@link #SAP_START_FAILURE_GENERAL}, * {@link #SAP_START_FAILURE_NO_CHANNEL}, From 3ae57dd9341981454494c4f561acaea6b03287bb Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 16 Jun 2021 03:27:23 -0400 Subject: [PATCH 049/285] Hide notification when service is inactive --- .../mygod/vpnhotspot/ServiceNotification.kt | 44 ++++++++++++------- mobile/src/main/res/values-zh-rCN/strings.xml | 1 + mobile/src/main/res/values/strings.xml | 3 +- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index 6609c2c0..587a0160 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -12,25 +12,29 @@ import be.mygod.vpnhotspot.App.Companion.app import java.util.* object ServiceNotification { - private const val CHANNEL = "tethering" - private const val CHANNEL_ID = 1 + private const val CHANNEL_ACTIVE = "tethering" + private const val CHANNEL_INACTIVE = "tethering-inactive" + private const val NOTIFICATION_ID = 1 private val deviceCountsMap = WeakHashMap>() private val inactiveMap = WeakHashMap>() private val manager = app.getSystemService()!! private fun buildNotification(context: Context): Notification { - val builder = NotificationCompat.Builder(context, CHANNEL) - .setWhen(0) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setColor(ContextCompat.getColor(context, R.color.colorPrimary)) - .setContentTitle(context.getText(R.string.notification_tethering_title)) - .setSmallIcon(R.drawable.ic_quick_settings_tile_on) - .setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) val deviceCounts = deviceCountsMap.values.flatMap { it.entries }.sortedBy { it.key } val inactive = inactiveMap.values.flatten() + val isInactive = inactive.isNotEmpty() && deviceCounts.isEmpty() + val builder = NotificationCompat.Builder(context, if (isInactive) CHANNEL_INACTIVE else CHANNEL_ACTIVE).apply { + setWhen(0) + setCategory(NotificationCompat.CATEGORY_SERVICE) + color = ContextCompat.getColor(context, R.color.colorPrimary) + setContentTitle(context.getText(R.string.notification_tethering_title)) + setSmallIcon(R.drawable.ic_quick_settings_tile_on) + setContentIntent(PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)) + setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + priority = if (isInactive) NotificationCompat.PRIORITY_MIN else NotificationCompat.PRIORITY_LOW + } var lines = deviceCounts.map { (dev, size) -> context.resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev) } @@ -54,23 +58,29 @@ object ServiceNotification { synchronized(this) { deviceCountsMap[service] = deviceCounts if (inactive.isEmpty()) inactiveMap.remove(service) else inactiveMap[service] = inactive - service.startForeground(CHANNEL_ID, buildNotification(service)) + service.startForeground(NOTIFICATION_ID, buildNotification(service)) } } fun stopForeground(service: Service) = synchronized(this) { deviceCountsMap.remove(service) if (deviceCountsMap.isEmpty()) service.stopForeground(true) else { service.stopForeground(false) - manager.notify(CHANNEL_ID, buildNotification(service)) + manager.notify(NOTIFICATION_ID, buildNotification(service)) } } fun updateNotificationChannels() { if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) { - val tethering = NotificationChannel(CHANNEL, - app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW) - tethering.lockscreenVisibility = Notification.VISIBILITY_PUBLIC - manager.createNotificationChannel(tethering) + NotificationChannel(CHANNEL_ACTIVE, + app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW).apply { + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + manager.createNotificationChannel(this) + } + NotificationChannel(CHANNEL_INACTIVE, + app.getText(R.string.notification_channel_monitor), NotificationManager.IMPORTANCE_LOW).apply { + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + manager.createNotificationChannel(this) + } // remove old service channels manager.deleteNotificationChannel("hotspot") manager.deleteNotificationChannel("repeater") diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index e727ef85..fb3af0f6 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -152,6 +152,7 @@ VPN 共享已启用 VPN 共享服务 + 监视不活跃接口 %d 个设备已连接到 %s diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 4505061f..fe9c1dbd 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -170,8 +170,9 @@ Restart this app to apply this setting. Exit - VPN tethering active + VPN tethering VPN Tethering Service + Monitor Inactive Interfaces %d device connected to %s %d devices connected to %s From 4733b294cfa3ffc1585538169e3724b61627db6d Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 18 Jun 2021 02:23:08 -0400 Subject: [PATCH 050/285] Fix removing monitored interface --- .../vpnhotspot/manage/TetheringFragment.kt | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index ba2e52af..b799f9f8 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -64,10 +64,13 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick } private val wifiManagerLegacy by lazy { TetherManager.WifiLegacy(this@TetheringFragment) } - private var enabledIfaces = emptyList() + var activeIfaces = emptyList() + var localOnlyIfaces = emptyList() + var erroredIfaces = emptyList() private var listDeferred = CompletableDeferred>(emptyList()) - private fun updateEnabledTypes() { - this@TetheringFragment.enabledTypes = enabledIfaces.map { TetherType.ofInterface(it) }.toSet() + fun updateEnabledTypes() { + this@TetheringFragment.enabledTypes = + (activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet() } val lastErrors = mutableMapOf() @@ -75,20 +78,16 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick if (error == 0) lastErrors.remove(ifName) else lastErrors[ifName] = error } - suspend fun notifyInterfaceChanged(lastList: List? = null) { - @Suppress("NAME_SHADOWING") val lastList = lastList ?: listDeferred.await() - val first = lastList.indexOfFirst { it is InterfaceManager } - if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1) - } suspend fun notifyTetherTypeChanged() { updateEnabledTypes() val lastList = listDeferred.await() - notifyInterfaceChanged(lastList) - val first = lastList.indexOfLast { it !is TetherManager } + 1 + var first = lastList.indexOfFirst { it is InterfaceManager } + if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1) + first = lastList.indexOfLast { it !is TetherManager } + 1 notifyItemRangeChanged(first, lastList.size - first) } - fun update(activeIfaces: List, localOnlyIfaces: List, erroredIfaces: List) { + fun update() { val deferred = CompletableDeferred>() listDeferred = deferred ifaceLookup = try { @@ -97,8 +96,6 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick Timber.d(e) emptyMap() } - enabledIfaces = activeIfaces + localOnlyIfaces - updateEnabledTypes() val list = ArrayList() if (Services.p2p != null) list.add(repeaterManager) @@ -151,9 +148,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick var binder: TetheringService.Binder? = null private val adapter = ManagerAdapter() private val receiver = broadcastReceiver { _, intent -> - adapter.update(intent.tetheredIfaces ?: return@broadcastReceiver, - intent.localOnlyTetheredIfaces ?: return@broadcastReceiver, - intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER) ?: return@broadcastReceiver) + adapter.activeIfaces = intent.tetheredIfaces ?: return@broadcastReceiver + adapter.localOnlyIfaces = intent.localOnlyTetheredIfaces ?: return@broadcastReceiver + adapter.erroredIfaces = intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER) + ?: return@broadcastReceiver + adapter.updateEnabledTypes() + adapter.update() } private fun updateMonitorList(canMonitor: List = emptyList()) { @@ -260,7 +260,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) binding.interfaces.itemAnimator = DefaultItemAnimator() binding.interfaces.adapter = adapter - adapter.update(emptyList(), emptyList(), emptyList()) + adapter.update() ServiceForegroundConnector(this, this, TetheringService::class) (activity as MainActivity).binding.toolbar.apply { inflateMenu(R.menu.toolbar_tethering) @@ -284,9 +284,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick override fun onServiceConnected(name: ComponentName?, service: IBinder?) { binder = service as TetheringService.Binder - service.routingsChanged[this] = { - lifecycleScope.launchWhenStarted { adapter.notifyInterfaceChanged() } - } + service.routingsChanged[this] = { lifecycleScope.launchWhenStarted { adapter.update() } } requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) if (Build.VERSION.SDK_INT >= 30) { TetheringManager.registerTetheringEventCallback(null, adapter) From 907283c53a1234b4561df295be4a469f9d989226 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 11 Jun 2021 00:49:54 -0400 Subject: [PATCH 051/285] Be more careful about using unsupported APIs --- .../be/mygod/vpnhotspot/net/wifi/WifiApManager.kt | 4 ++++ .../src/main/java/be/mygod/vpnhotspot/util/Utils.kt | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 69715336..82234eed 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -200,6 +200,10 @@ object WifiApManager { private val cancelLocalOnlyHotspotRequest by lazy { WifiManager::class.java.getDeclaredMethod("cancelLocalOnlyHotspotRequest") } + /** + * This is the only way to unregister requests besides app exiting. + * Therefore, we are happy with crashing the app if reflection fails. + */ @RequiresApi(26) fun cancelLocalOnlyHotspotRequest() = cancelLocalOnlyHotspotRequest(Services.wifi) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 2b024db6..d530d8ba 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -159,10 +159,14 @@ private val newLookup by lazy @TargetApi(26) { * See also: https://stackoverflow.com/a/49532463/2245107 */ fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Method, args: Array?) = when { - Build.VERSION.SDK_INT >= 26 && method.isDefault -> newLookup.newInstance(interfaceClass, 0xf) // ALL_MODES - .`in`(interfaceClass).unreflectSpecial(method, interfaceClass).bindTo(proxy).run { - if (args == null) invokeWithArguments() else invokeWithArguments(*args) - } + Build.VERSION.SDK_INT >= 26 && method.isDefault -> try { + newLookup.newInstance(interfaceClass, 0xf) // ALL_MODES + } catch (e: ReflectiveOperationException) { + Timber.w(e) + MethodHandles.lookup().`in`(interfaceClass) + }.unreflectSpecial(method, interfaceClass).bindTo(proxy).run { + if (args == null) invokeWithArguments() else invokeWithArguments(*args) + } // otherwise, we just redispatch it to InvocationHandler method.declaringClass.isAssignableFrom(javaClass) -> when { method.declaringClass == Object::class.java -> when (method.name) { From 681b23437ea7a8c1fce99c7b55dfdf4b6d790bf9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 18 Jun 2021 02:23:27 -0400 Subject: [PATCH 052/285] Update dependencies --- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- mobile/build.gradle.kts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 657363fe..359caba9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { dependencies { classpath(kotlin("gradle-plugin", "1.5.10")) - classpath("com.android.tools.build:gradle:7.0.0-beta03") + classpath("com.android.tools.build:gradle:7.0.0-beta04") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.0") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") classpath("com.google.gms:google-services:4.3.8") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf5..69a97150 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 91c74d2e..815477c0 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -72,9 +72,9 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation("androidx.appcompat:appcompat:1.3.0") // https://issuetracker.google.com/issues/151603528 implementation("androidx.browser:browser:1.3.0") - implementation("androidx.core:core-ktx:1.6.0-beta02") + implementation("androidx.core:core-ktx:1.6.0-rc01") implementation("androidx.emoji:emoji:1.1.0") - implementation("androidx.fragment:fragment-ktx:1.3.4") + implementation("androidx.fragment:fragment-ktx:1.3.5") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion") From 7e5d527caecd02136749d49f3415b61594faae94 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 18 Jun 2021 02:54:09 -0400 Subject: [PATCH 053/285] v2.11.9 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 815477c0..ca6e68a2 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 261 - versionName = "2.11.8" + versionCode = 262 + versionName = "2.11.9" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From 6af69686043a71eda00a6ad5677e7896180a7694 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 20 Jun 2021 14:11:38 -0400 Subject: [PATCH 054/285] Refine SettingsGlobalPut --- mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt index 34773089..10179637 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/MiscCommands.kt @@ -213,8 +213,8 @@ data class SettingsGlobalPut(val name: String, val value: String) : RootCommandN override suspend fun execute() = withContext(Dispatchers.IO) { val process = ProcessBuilder("settings", "put", "global", name, value).fixPath(true).start() val error = process.inputStream.bufferedReader().readText() - check(process.waitFor() == 0) - if (error.isNotEmpty()) throw RemoteException(error) + val exit = process.waitFor() + if (exit != 0 || error.isNotEmpty()) throw RemoteException("Process exited with $exit: $error") null } } From 929a6278a4cb0b0ce935e391af0ce83c66a25a93 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 19:10:41 -0400 Subject: [PATCH 055/285] Update dependencies --- build.gradle.kts | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- mobile/build.gradle.kts | 14 +++++++------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 359caba9..9a4b13e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,9 +9,9 @@ buildscript { } dependencies { - classpath(kotlin("gradle-plugin", "1.5.10")) - classpath("com.android.tools.build:gradle:7.0.0-beta04") - classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.0") + classpath(kotlin("gradle-plugin", "1.5.20")) + classpath("com.android.tools.build:gradle:7.0.0-beta05") + classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") classpath("com.google.gms:google-services:4.3.8") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a97150..05679dc3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c..744e882e 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index ca6e68a2..bee3ee8e 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -72,7 +72,7 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation("androidx.appcompat:appcompat:1.3.0") // https://issuetracker.google.com/issues/151603528 implementation("androidx.browser:browser:1.3.0") - implementation("androidx.core:core-ktx:1.6.0-rc01") + implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.emoji:emoji:1.1.0") implementation("androidx.fragment:fragment-ktx:1.3.5") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") @@ -83,18 +83,18 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.android.billingclient:billing-ktx:4.0.0") implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") - implementation("com.google.android.material:material:1.4.0-rc01") + implementation("com.google.android.material:material:1.4.0") implementation("com.google.firebase:firebase-analytics-ktx:19.0.0") - implementation("com.google.firebase:firebase-crashlytics:18.0.1") + implementation("com.google.firebase:firebase-crashlytics:18.1.0") implementation("com.google.zxing:core:3.4.1") implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.linkedin.dexmaker:dexmaker:2.28.1") implementation("com.takisoft.preferencex:preferencex-simplemenu:1.1.0") implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.4") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.room:room-testing:$roomVersion") - androidTestImplementation("androidx.test:runner:1.3.0") - androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0") - androidTestImplementation("androidx.test.ext:junit-ktx:1.1.2") + androidTestImplementation("androidx.test:runner:1.4.0") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test.ext:junit-ktx:1.1.3") } From 3f18c7f47de779fbeb7e93f3e443745d8b716e6e Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 19:16:11 -0400 Subject: [PATCH 056/285] Check for channel nullability --- mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt index 624b9870..abe09ae3 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt @@ -504,7 +504,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene if (group == null) emptyMap() else mapOf(Pair(group.`interface`, group.clientList?.size ?: 0))) private fun removeGroup() { - p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { + p2pManager.removeGroup(channel ?: return, object : WifiP2pManager.ActionListener { override fun onSuccess() { launch { cleanLocked() } } From 91afda8fdf6d7a20ab6e33bf982f07011a0910f1 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 19:19:17 -0400 Subject: [PATCH 057/285] Prevent invalid paste crashing app --- .../java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 be72f8f4..cb23f5b7 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 @@ -317,7 +317,7 @@ class WifiApDialogFragment : AlertDialogFragment Date: Sat, 10 Jul 2021 19:33:12 -0400 Subject: [PATCH 058/285] Fix snackbar being behind dialog --- .../java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 db3bf1b7..2307253c 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 @@ -16,6 +16,7 @@ import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Spinner +import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar @@ -33,7 +34,6 @@ import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor import be.mygod.vpnhotspot.util.QRCodeDialog 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 @@ -390,7 +390,7 @@ class WifiApDialogFragment : AlertDialogFragment { From f7a2507e797d4da5c1093a25ad59db0f5f935410 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 20:37:33 -0400 Subject: [PATCH 059/285] Add section headers to wifi ap config dialog --- mobile/build.gradle.kts | 2 +- .../net/wifi/WifiApDialogFragment.kt | 31 +- mobile/src/main/res/layout/dialog_wifi_ap.xml | 320 ++++++++++-------- mobile/src/main/res/values-zh-rCN/strings.xml | 2 + mobile/src/main/res/values/strings.xml | 2 + mobile/src/main/res/values/styles.xml | 11 + 6 files changed, 206 insertions(+), 162 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 836bce8e..ac996760 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -83,7 +83,7 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.android.billingclient:billing-ktx:4.0.0") implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") - implementation("com.google.android.material:material:1.4.0") + implementation("com.google.android.material:material:1.5.0-alpha01") implementation("com.google.firebase:firebase-analytics-ktx:19.0.0") implementation("com.google.firebase:firebase-crashlytics:18.1.0") implementation("com.google.zxing:core:3.4.1") 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 2307253c..4a280766 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 @@ -177,31 +177,24 @@ class WifiApDialogFragment : AlertDialogFragment= 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) { - dialogView.maxClientWrapper.isGone = true - dialogView.clientUserControl.isGone = true - dialogView.blockedListWrapper.isGone = true - dialogView.allowedListWrapper.isGone = true - } else { + 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.bridgedMode.isGone = true + dialogView.bridgedModeOpportunisticShutdown.isGone = true + } + } else dialogView.bandGroup.isGone = true + if (!arg.p2pMode && Build.VERSION.SDK_INT >= 30) { dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment) dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment) dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment) - } + } else dialogView.accessControlGroup.isGone = true + dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment) + if (arg.p2pMode) dialogView.hiddenSsid.isGone = true 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.bridgedMode.isGone = true - dialogView.bridgedModeOpportunisticShutdown.isGone = true dialogView.ieee80211ax.isGone = true dialogView.userConfig.isGone = true } diff --git a/mobile/src/main/res/layout/dialog_wifi_ap.xml b/mobile/src/main/res/layout/dialog_wifi_ap.xml index 650b82a5..cff941e6 100644 --- a/mobile/src/main/res/layout/dialog_wifi_ap.xml +++ b/mobile/src/main/res/layout/dialog_wifi_ap.xml @@ -121,15 +121,25 @@ android:inputType="number" android:maxLength="19" /> + + + - - + + + + + + + + + + + + - + + + + + android:text="@string/wifi_client_user_control" /> + + + + + + + + + + + + - + android:text="@string/wifi_mac_randomization" /> + + + - - - - - - - - - - - - - - - - - - - diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index fb3af0f6..1e7be6c6 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -185,6 +185,8 @@ "5 GHz 频段" 6 GHz 频段 60 GHz 频段 + 访问控制 + 高级接入点设置 "MAC 地址" "隐藏的网络" 允许连接设备数上限 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index fe9c1dbd..0fbfb5fc 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -208,6 +208,8 @@ 5 GHz Band 6 GHz Band 60 GHz Band + Access Control + Advanced AP Options MAC address Hidden network Maximum number of clients diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index 013d57fa..8f249d97 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -43,5 +43,16 @@ 4dip 18sp + + From 04001647f5b1d7b87b9f19ed3803adb969504fe9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 21:19:38 -0400 Subject: [PATCH 060/285] Try to parse bytes as String if possible --- .../mygod/vpnhotspot/net/MacAddressCompat.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt index 6eb9ae62..7853b199 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/MacAddressCompat.kt @@ -29,21 +29,19 @@ value class MacAddressCompat(val addr: Long) { * @return the MacAddress corresponding to the given byte array representation. * @throws IllegalArgumentException if the given byte array is not a valid representation. */ - fun fromBytes(addr: ByteArray) = ByteBuffer.allocate(Long.SIZE_BYTES).run { - order(ByteOrder.LITTLE_ENDIAN) - put(when (addr.size) { - ETHER_ADDR_LEN -> addr + fun fromBytes(addr: ByteArray): MacAddressCompat { + val buffer = when (addr.size) { + ETHER_ADDR_LEN -> ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr) 8 -> { require(addr.take(2).all { it == 0.toByte() }) { "Unrecognized padding " + addr.joinToString(":") { "%02x".format(it) } } - addr.drop(2).toByteArray() + ByteBuffer.allocate(Long.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).put(addr, 2, ETHER_ADDR_LEN) } - else -> throw IllegalArgumentException(addr.joinToString(":") { "%02x".format(it) } + - " was not a valid MAC address") - }) - rewind() - MacAddressCompat(long) + else -> return fromString(String(addr)) + } + buffer.rewind() + return MacAddressCompat(buffer.long) } /** * Creates a MacAddress from the given String representation. A valid String representation From 7f3e57305542b63a047f9d7ae0f00af1c775d6d2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 21:35:48 -0400 Subject: [PATCH 061/285] Suppress more stupid Android 6 bugs --- .../be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt index f329461a..c6548a16 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt @@ -55,7 +55,7 @@ object DefaultNetworkMonitor : UpstreamMonitor() { Services.connectivity.registerDefaultNetworkCallback(networkCallback) } else try { Services.connectivity.requestNetwork(networkRequest, networkCallback) - } catch (e: SecurityException) { + } catch (e: RuntimeException) { // SecurityException would be thrown in requestNetwork on Android 6.0 thanks to Google's stupid bug if (Build.VERSION.SDK_INT != 23) throw e GlobalScope.launch { callback.onFallback() } From ae2a9e80d143a652e75cd704761c97276096629b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 22:18:10 -0400 Subject: [PATCH 062/285] Update detekt --- detekt.yml | 65 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/detekt.yml b/detekt.yml index b5534fe8..87873f46 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,4 +1,4 @@ -# https://github.com/detekt/detekt/blob/v1.14.2/detekt-core/src/main/resources/default-detekt-config.yml +# https://github.com/detekt/detekt/blob/v1.17.1/detekt-core/src/main/resources/default-detekt-config.yml comments: active: false @@ -36,6 +36,8 @@ complexity: ignoreAnnotated: [] MethodOverloading: active: false + NamedArguments: + active: false NestedBlockDepth: active: true threshold: 4 @@ -66,6 +68,8 @@ coroutines: active: false RedundantSuspendModifier: active: true + SleepInsteadOfDelay: + active: true SuspendFunWithFlowReturnType: active: true @@ -113,8 +117,10 @@ exceptions: active: false NotImplementedDeclaration: active: true + ObjectExtendsThrowable: + active: true PrintStackTrace: - active: false + active: true RethrowCaughtException: active: false ReturnFromFinally: @@ -179,11 +185,13 @@ formatting: ImportOrdering: active: true autoCorrect: true - layout: 'idea' + layout: '*,java.**,javax.**,kotlin.**,^' Indentation: active: false MaximumLineLength: - active: false + active: true + maxLineLength: 120 + ignoreBackTickedIdentifier: false ModifierOrdering: active: true autoCorrect: true @@ -229,6 +237,9 @@ formatting: autoCorrect: true ParameterListWrapping: active: false + SpacingAroundAngleBrackets: + active: true + autoCorrect: true SpacingAroundColon: active: true autoCorrect: true @@ -256,6 +267,9 @@ formatting: SpacingAroundRangeOperator: active: true autoCorrect: true + SpacingAroundUnaryOperator: + active: true + autoCorrect: true SpacingBetweenDeclarationsWithAnnotations: active: false SpacingBetweenDeclarationsWithComments: @@ -306,12 +320,15 @@ naming: ignoreOverridden: true InvalidPackageDeclaration: active: true + excludes: ['buildSrc/**', '**/*.kts'] rootPackage: '' MatchingDeclarationName: active: true mustBeFirst: true MemberNameEqualsClassName: active: false + NoNameShadowing: + active: true NonBooleanPropertyPrefixedWithIs: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] @@ -327,7 +344,7 @@ naming: packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' TopLevelPropertyNaming: active: true - excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] + excludes: ['buildSrc/**', '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] constantPattern: '[A-Z][_A-Z0-9]*' propertyPattern: '[A-Za-z][_A-Za-z0-9]*' privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' @@ -359,14 +376,22 @@ performance: potential-bugs: active: true + CastToNullableType: + active: false Deprecation: active: true + DontDowncastCollectionTypes: + active: true + DoubleMutabilityForCollection: + active: true DuplicateCaseInWhenExpression: active: true EqualsAlwaysReturnsTrueOrFalse: active: true EqualsWithHashCodeExist: active: true + ExitOutsideMain: + active: true ExplicitGarbageCollectionCall: active: true HasPlatformType: @@ -398,15 +423,19 @@ potential-bugs: UnconditionalJumpStatementInLoop: active: true UnnecessaryNotNullOperator: - active: false + active: true UnnecessarySafeCall: - active: false + active: true + UnreachableCatchBlock: + active: true UnreachableCode: active: true UnsafeCallOnNullableType: active: true UnsafeCast: - active: false + active: true + UnusedUnaryOperator: + active: true UselessPostfixExpression: active: true WrongEqualsTypeParameter: @@ -422,6 +451,8 @@ style: active: false DataClassShouldBeImmutable: active: false + DestructuringDeclarationWithTooManyEntries: + active: false EqualsNullCall: active: true EqualsOnSignatureLine: @@ -446,6 +477,7 @@ style: methods: ['kotlin.io.println', 'kotlin.io.print'] ForbiddenPublicDataClass: active: true + excludes: ['**'] ignorePackages: ['*.internal', '*.internal.*'] ForbiddenVoid: active: true @@ -454,12 +486,15 @@ style: FunctionOnlyReturningConstant: active: true ignoreOverridableFunction: true + ignoreActualFunction: true excludedFunctions: 'describeContents' excludeAnnotatedFunction: ['dagger.Provides'] LibraryCodeMustSpecifyReturnType: active: true + excludes: ['**'] LibraryEntitiesShouldNotBePublic: active: true + excludes: ['**'] LoopWithTooManyJumpStatements: active: true maxJumpCount: 1 @@ -479,12 +514,16 @@ style: active: true ModifierOrder: active: true + MultilineLambdaItParameter: + active: false NestedClassesVisibility: active: true NewLineAtEndOfFile: active: true NoTabs: active: true + ObjectLiteralToLambda: + active: true OptionalAbstractKeyword: active: true OptionalUnit: @@ -497,6 +536,8 @@ style: active: true RedundantExplicitType: active: true + RedundantHigherOrderMapUsage: + active: true RedundantVisibilityModifierRule: active: true ReturnCount: @@ -520,6 +561,8 @@ style: active: true UnnecessaryApply: active: true + UnnecessaryFilter: + active: true UnnecessaryInheritance: active: true UnnecessaryLet: @@ -545,8 +588,14 @@ style: active: false UseEmptyCounterpart: active: true + UseIfEmptyOrIfBlank: + active: true UseIfInsteadOfWhen: active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: + active: true UseRequire: active: true UseRequireNotNull: From 556ae9eb45af6e748e8ea668dbdbfed3d190831f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 10 Jul 2021 22:28:39 -0400 Subject: [PATCH 063/285] v2.12.4 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index ac996760..476ace50 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 274 - versionName = "2.12.3" + versionCode = 275 + versionName = "2.12.4" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From 1280df3d0cab6c809a1a737dc936aab1a8616bc3 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 14 Jul 2021 21:47:14 -0400 Subject: [PATCH 064/285] Update to compile against API 31 --- build.gradle.kts | 2 +- gradle.properties | 1 - mobile/build.gradle.kts | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9a4b13e0..eb5f95ad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath(kotlin("gradle-plugin", "1.5.20")) + classpath(kotlin("gradle-plugin", "1.5.21")) classpath("com.android.tools.build:gradle:7.0.0-beta05") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") diff --git a/gradle.properties b/gradle.properties index feb18146..6fb3e361 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,6 @@ android.databinding.incremental=true android.enableJetifier=true android.enableR8.fullMode=true android.enableResourceOptimizations=false -android.injected.testOnly=false android.useAndroidX=true kapt.incremental.apt=true org.gradle.jvmargs=-Xmx1536m diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 476ace50..ca30bc95 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -11,18 +11,18 @@ plugins { android { val javaVersion = JavaVersion.VERSION_1_8 val targetSdk = 29 - buildToolsVersion = "31.0.0-rc5" + buildToolsVersion = "31.0.0" compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = javaVersion targetCompatibility = javaVersion } - compileSdkPreview = "android-S" + compileSdk = 31 kotlinOptions.jvmTarget = javaVersion.toString() defaultConfig { applicationId = "be.mygod.vpnhotspot" minSdk = 21 - if (targetSdk == 31) targetSdkPreview = "S" else this.targetSdk = targetSdk + this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) versionCode = 275 versionName = "2.12.4" From 0c735ce92fc8212b5d193a3e5c78a00e74b0fc1f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 20:15:11 -0400 Subject: [PATCH 065/285] Update private API commit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c80287a..82a59b9e 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ _a.k.a. things that can go wrong if this app doesn't work._ This is a list of stuff that might impact this app's functionality if unavailable. This is only meant to be an index. You can read more in the source code. -API restrictions are updated up to [commit `ebe7044`](https://android.googlesource.com/platform/prebuilts/runtime/+/ebe7044/appcompat/hiddenapi-flags.csv). +API restrictions are updated up to [SHA-256 checksum `156715dfa705a048926dca876d731d72604df32e8bcac055af32866b50bc2cc8`](https://dl.google.com/developers/android/sc/non-sdk/hiddenapi-flags.csv). Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used) @@ -283,8 +283,8 @@ Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded * (since API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_*:I,sdk,system-api,test-api` * (on API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_INVALID:I,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/SoftApInfo;->getAutoShutdownTimeoutMillis()J,sdk,system-api,test-api` -* (since API 31) `Landroid/net/wifi/SoftApInfo;->getBssid()Landroid/net/MacAddress;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/SoftApInfo;->getBandwidth()I,sdk,system-api,test-api` +* (since API 31) `Landroid/net/wifi/SoftApInfo;->getBssid()Landroid/net/MacAddress;,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/SoftApInfo;->getFrequency()I,sdk,system-api,test-api` * (since API 31) `Landroid/net/wifi/SoftApInfo;->getWifiStandard()I,sdk,system-api,test-api` * (since API 30) `Landroid/net/wifi/WifiClient;->getMacAddress()Landroid/net/MacAddress;,sdk,system-api,test-api` From 37eeeb43248045e83fec0511ef01132579e2a326 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 20:26:51 -0400 Subject: [PATCH 066/285] Update dependencies --- build.gradle.kts | 2 +- mobile/build.gradle.kts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index eb5f95ad..5a06bcdf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { dependencies { classpath(kotlin("gradle-plugin", "1.5.21")) - classpath("com.android.tools.build:gradle:7.0.0-beta05") + classpath("com.android.tools.build:gradle:7.0.0-rc01") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") classpath("com.google.gms:google-services:4.3.8") diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index ca30bc95..80bc2356 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -70,11 +70,11 @@ dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") kapt("androidx.room:room-compiler:$roomVersion") implementation(kotlin("stdlib-jdk8")) - implementation("androidx.appcompat:appcompat:1.3.0") // https://issuetracker.google.com/issues/151603528 + implementation("androidx.appcompat:appcompat:1.3.1") // https://issuetracker.google.com/issues/151603528 implementation("androidx.browser:browser:1.3.0") implementation("androidx.core:core-ktx:1.6.0") implementation("androidx.emoji:emoji:1.1.0") - implementation("androidx.fragment:fragment-ktx:1.3.5") + implementation("androidx.fragment:fragment-ktx:1.3.6") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion") @@ -85,7 +85,7 @@ dependencies { implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") implementation("com.google.android.material:material:1.5.0-alpha01") implementation("com.google.firebase:firebase-analytics-ktx:19.0.0") - implementation("com.google.firebase:firebase-crashlytics:18.1.0") + implementation("com.google.firebase:firebase-crashlytics:18.2.0") implementation("com.google.zxing:core:3.4.1") implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.linkedin.dexmaker:dexmaker:2.28.1") From b03303c73ac3f77191a79a56979ddaa4236d9d32 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 20:33:30 -0400 Subject: [PATCH 067/285] Drop support for old Android 12 betas --- .../vpnhotspot/client/ClientViewModel.kt | 6 +-- .../vpnhotspot/manage/BluetoothTethering.kt | 3 +- .../mygod/vpnhotspot/manage/TetherManager.kt | 11 ++--- .../net/wifi/SoftApConfigurationCompat.kt | 46 +++++++++---------- .../net/wifi/WifiApDialogFragment.kt | 11 ++--- .../vpnhotspot/net/wifi/WifiApManager.kt | 2 +- .../mygod/vpnhotspot/root/WifiApCommands.kt | 4 +- 7 files changed, 40 insertions(+), 43 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt index 53a848dd..69d08aad 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt @@ -4,10 +4,10 @@ import android.content.ComponentName import android.content.IntentFilter import android.content.ServiceConnection import android.net.wifi.p2p.WifiP2pDevice +import android.os.Build import android.os.IBinder import android.os.Parcelable import androidx.annotation.RequiresApi -import androidx.core.os.BuildCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.MutableLiveData @@ -87,10 +87,10 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb override fun onStart(owner: LifecycleOwner) { app.registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) IpNeighbourMonitor.registerCallback(this, false) - if (BuildCompat.isAtLeastS()) WifiApCommands.registerSoftApCallback(this) + if (Build.VERSION.SDK_INT >= 31) WifiApCommands.registerSoftApCallback(this) } override fun onStop(owner: LifecycleOwner) { - if (BuildCompat.isAtLeastS()) WifiApCommands.unregisterSoftApCallback(this) + if (Build.VERSION.SDK_INT >= 31) WifiApCommands.unregisterSoftApCallback(this) IpNeighbourMonitor.unregisterCallback(this) app.unregisterReceiver(receiver) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 1dce98e2..bbb9e823 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -12,7 +12,6 @@ import android.content.IntentFilter import android.os.Build import androidx.annotation.RequiresApi import androidx.core.content.getSystemService -import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.util.broadcastReceiver @@ -91,7 +90,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : check(adapter.getProfileProxy(context, this, PAN)) proxyCreated = true } catch (e: SecurityException) { - if (BuildCompat.isAtLeastS()) Timber.d(e.readableMessage) else Timber.w(e) + if (Build.VERSION.SDK_INT >= 31) Timber.d(e.readableMessage) else Timber.w(e) activeFailureCause = e } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 93caf1c3..2b9ee1ff 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -15,7 +15,6 @@ import android.view.View import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.net.toUri -import androidx.core.os.BuildCompat import androidx.core.view.updatePaddingRelative import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -198,7 +197,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), val numClients = numClients val maxClients = capability.maxSupportedClients var features = capability.supportedFeatures - if (BuildCompat.isAtLeastS()) for ((flag, band) in arrayOf( + if (Build.VERSION.SDK_INT >= 31) for ((flag, band) in arrayOf( SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED to SoftApConfigurationCompat.BAND_2GHZ, SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED to SoftApConfigurationCompat.BAND_5GHZ, SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED to SoftApConfigurationCompat.BAND_6GHZ, @@ -214,7 +213,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), R.string.tethering_manage_wifi_feature_ap_mac_randomization)) if (Services.wifi.isStaApConcurrencySupported) yield(parent.getText( R.string.tethering_manage_wifi_feature_sta_ap_concurrency)) - if (BuildCompat.isAtLeastS()) { + if (Build.VERSION.SDK_INT >= 31) { if (Services.wifi.isBridgedApConcurrencySupported) yield(parent.getText( R.string.tethering_manage_wifi_feature_bridged_ap_concurrency)) if (Services.wifi.isStaBridgedApConcurrencySupported) yield(parent.getText( @@ -228,7 +227,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), }.joinToSpanned().let { if (it.isEmpty()) parent.getText(R.string.tethering_manage_wifi_no_features) else it }) - if (BuildCompat.isAtLeastS()) { + if (Build.VERSION.SDK_INT >= 31) { val list = SoftApConfigurationCompat.BAND_TYPES.map { band -> val channels = capability.getSupportedChannelList(band) if (channels.isNotEmpty()) StringBuilder().apply { @@ -272,7 +271,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), val frequency = info.frequency val channel = SoftApConfigurationCompat.frequencyToChannel(frequency) val bandwidth = SoftApInfo.channelWidthLookup(info.bandwidth, true) - if (BuildCompat.isAtLeastS()) { + if (Build.VERSION.SDK_INT >= 31) { var bssid = makeMacSpan(info.bssid.toString()) info.apInstanceIdentifier?.let { // take the fast route if possible bssid = if (bssid is String) "$bssid%$it" else SpannableStringBuilder(bssid).append("%$it") @@ -315,7 +314,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), onTetheringStarted() // force flush } override fun onResume(owner: LifecycleOwner) { - if (!BuildCompat.isAtLeastS() || parent.requireContext().checkSelfPermission( + if (Build.VERSION.SDK_INT < 31 || parent.requireContext().checkSelfPermission( Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { tethering.ensureInit(parent.requireContext()) } else if (parent.shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) { 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 97b18594..fc4ee751 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 @@ -8,7 +8,6 @@ import android.os.Build import android.os.Parcelable import android.util.SparseIntArray import androidx.annotation.RequiresApi -import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor @@ -64,7 +63,7 @@ data class SoftApConfigurationCompat( const val BAND_60GHZ = 8 const val BAND_LEGACY = BAND_2GHZ or BAND_5GHZ val BAND_TYPES by lazy { - if (BuildCompat.isAtLeastS()) try { + if (Build.VERSION.SDK_INT >= 31) try { return@lazy UnblockCentral.SoftApConfiguration_BAND_TYPES } catch (e: ReflectiveOperationException) { Timber.w(e) @@ -314,25 +313,26 @@ data class SoftApConfigurationCompat( @RequiresApi(30) @Suppress("UNCHECKED_CAST") fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat( - ssid, - bssid?.toCompat()?.addr, - passphrase, - isHiddenSsid, - if (BuildCompat.isAtLeastS()) getChannels(this) as SparseIntArray else SparseIntArray(1).also { - it.append(getBand(this) as Int, getChannel(this) as Int) - }, - securityType, - getMaxNumberOfClients(this) as Int, - isAutoShutdownEnabled(this) as Boolean, - getShutdownTimeoutMillis(this) as Long, - isClientControlByUserEnabled(this) as Boolean, - getBlockedClientList(this) as List, - getAllowedClientList(this) as List, - if (BuildCompat.isAtLeastS()) getMacRandomizationSetting(this) as Int else RANDOMIZATION_PERSISTENT, - !BuildCompat.isAtLeastS() || isBridgedModeOpportunisticShutdownEnabled(this) as Boolean, - !BuildCompat.isAtLeastS() || isIeee80211axEnabled(this) as Boolean, - !BuildCompat.isAtLeastS() || isUserConfiguration(this) as Boolean, - this) + ssid, + bssid?.toCompat()?.addr, + passphrase, + isHiddenSsid, + if (Build.VERSION.SDK_INT >= 31) getChannels(this) as SparseIntArray else SparseIntArray(1).also { + it.append(getBand(this) as Int, getChannel(this) as Int) + }, + securityType, + getMaxNumberOfClients(this) as Int, + isAutoShutdownEnabled(this) as Boolean, + getShutdownTimeoutMillis(this) as Long, + isClientControlByUserEnabled(this) as Boolean, + getBlockedClientList(this) as List, + getAllowedClientList(this) as List, + if (Build.VERSION.SDK_INT >= 31) getMacRandomizationSetting(this) as Int else RANDOMIZATION_PERSISTENT, + Build.VERSION.SDK_INT < 31 || isBridgedModeOpportunisticShutdownEnabled(this) as Boolean, + Build.VERSION.SDK_INT < 31 || isIeee80211axEnabled(this) as Boolean, + Build.VERSION.SDK_INT < 31 || isUserConfiguration(this) as Boolean, + this, + ) } @Suppress("DEPRECATION") @@ -438,7 +438,7 @@ data class SoftApConfigurationCompat( setSsid(builder, ssid) setPassphrase(builder, if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) null else passphrase, securityType) - if (BuildCompat.isAtLeastS()) setChannels(builder, channels) else { + if (Build.VERSION.SDK_INT >= 31) setChannels(builder, channels) else { val (band, channel) = requireSingleBand() if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band) } @@ -450,7 +450,7 @@ data class SoftApConfigurationCompat( setHiddenSsid(builder, isHiddenSsid) setAllowedClientList(builder, allowedClientList) setBlockedClientList(builder, blockedClientList) - if (BuildCompat.isAtLeastS()) { + if (Build.VERSION.SDK_INT >= 31) { setMacRandomizationSetting(builder, macRandomizationSetting) setBridgedModeOpportunisticShutdownEnabled(builder, isBridgedModeOpportunisticShutdownEnabled) setIeee80211axEnabled(builder, isIeee80211axEnabled) 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 4a280766..0f68d590 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 @@ -20,7 +20,6 @@ import android.widget.Toast 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 @@ -117,7 +116,7 @@ class WifiApDialogFragment : AlertDialogFragment= 0) channels.append(band, channel) } - if (!arg.p2pMode && BuildCompat.isAtLeastS() && dialogView.bridgedMode.isChecked) { + if (!arg.p2pMode && Build.VERSION.SDK_INT >= 31 && dialogView.bridgedMode.isChecked) { this.channels = channels } else optimizeChannels(channels) } @@ -179,7 +178,7 @@ class WifiApDialogFragment : AlertDialogFragment= 30 && !arg.p2pMode) dialogView.band6G.configure(channels6G) else dialogView.bandWrapper6G.isGone = true - if (BuildCompat.isAtLeastS() && !arg.p2pMode) dialogView.band60G.configure(channels60G) else { + if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) dialogView.band60G.configure(channels60G) else { dialogView.bandWrapper60G.isGone = true dialogView.bridgedMode.isGone = true dialogView.bridgedModeOpportunisticShutdown.isGone = true @@ -193,8 +192,8 @@ class WifiApDialogFragment : AlertDialogFragment= 29) dialogView.macRandomization.isEnabled = false - else if (arg.p2pMode || !BuildCompat.isAtLeastS()) dialogView.macRandomization.isGone = true - if (arg.p2pMode || !BuildCompat.isAtLeastS()) { + else if (arg.p2pMode || Build.VERSION.SDK_INT < 31) dialogView.macRandomization.isGone = true + if (arg.p2pMode || Build.VERSION.SDK_INT < 31) { dialogView.ieee80211ax.isGone = true dialogView.userConfig.isGone = true } @@ -302,7 +301,7 @@ class WifiApDialogFragment : AlertDialogFragment option5G is ChannelOption.Disabled } } - Build.VERSION.SDK_INT == 30 && !BuildCompat.isAtLeastS() -> { + Build.VERSION.SDK_INT == 30 -> { var expected = 1 var set = 0 for (s in arrayOf(dialogView.band2G, dialogView.band5G, dialogView.band6G)) when (s.selectedItem) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 5bef6e2d..0a83397f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -287,7 +287,7 @@ object WifiApManager { if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") val arg = args!![0] if (arg is List<*>) { - if (!BuildCompat.isAtLeastS()) Timber.w(Exception("Unexpected onInfoChanged API 31+")) + if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) @Suppress("UNCHECKED_CAST") callback.onInfoChanged(arg as List) } else { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt index e05e7f10..e7db4c6d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/root/WifiApCommands.kt @@ -2,9 +2,9 @@ package be.mygod.vpnhotspot.root import android.annotation.TargetApi import android.content.ClipData +import android.os.Build import android.os.Parcelable import androidx.annotation.RequiresApi -import androidx.core.os.BuildCompat import be.mygod.librootkotlinx.ParcelableBoolean import be.mygod.librootkotlinx.RootCommand import be.mygod.librootkotlinx.RootCommandChannel @@ -129,7 +129,7 @@ object WifiApCommands { val client = WifiClient(parcel.client) val macAddress = client.macAddress var name = macAddress.toString() - if (BuildCompat.isAtLeastS()) client.apInstanceIdentifier?.let { name += "%$it" } + if (Build.VERSION.SDK_INT >= 31) client.apInstanceIdentifier?.let { name += "%$it" } val reason = WifiApManager.clientBlockLookup(parcel.blockedReason, true) Timber.i("$name blocked from connecting: $reason (${parcel.blockedReason})") SmartSnackbar.make(app.getString(R.string.tethering_manage_wifi_client_blocked, name, reason)).apply { From 403f7c0631b25ea2b66f012256fa3a562b676cd7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 20:36:02 -0400 Subject: [PATCH 068/285] Make sure the notification is cancelled --- .../main/java/be/mygod/vpnhotspot/ServiceNotification.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index 587a0160..cabea3aa 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -62,11 +62,9 @@ object ServiceNotification { } } fun stopForeground(service: Service) = synchronized(this) { - deviceCountsMap.remove(service) - if (deviceCountsMap.isEmpty()) service.stopForeground(true) else { - service.stopForeground(false) - manager.notify(NOTIFICATION_ID, buildNotification(service)) - } + val shutdown = deviceCountsMap.remove(service) != null && deviceCountsMap.isEmpty() + service.stopForeground(shutdown) + if (shutdown) manager.cancel(NOTIFICATION_ID) else manager.notify(NOTIFICATION_ID, buildNotification(service)) } fun updateNotificationChannels() { From dd1aced71c4381630cc9e08689f942424457170e Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 21:18:27 -0400 Subject: [PATCH 069/285] setIncludeOtherUidNetworks --- .../vpnhotspot/net/monitor/DefaultNetworkMonitor.kt | 9 +++++---- .../mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt | 3 ++- .../mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt | 10 ---------- .../be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt | 9 +++++---- .../src/main/java/be/mygod/vpnhotspot/util/Utils.kt | 12 +++++++++--- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt index 9f0739fd..6cbe6661 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt @@ -9,6 +9,7 @@ import android.os.Build import android.os.Handler import android.os.Looper import be.mygod.vpnhotspot.util.Services +import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -20,10 +21,10 @@ object DefaultNetworkMonitor : UpstreamMonitor() { * Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1: * https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e */ - private val networkRequest = networkRequestBuilder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - .build() + private val networkRequest = globalNetworkRequestBuilder().apply { + addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + }.build() private val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { val properties = Services.connectivity.getLinkProperties(network) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt index 41b17b2b..e9c9913a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt @@ -6,6 +6,7 @@ import android.net.Network import android.net.NetworkCapabilities import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.allInterfaceNames +import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber @@ -18,7 +19,7 @@ class InterfaceMonitor(private val ifaceRegex: String) : UpstreamMonitor() { Timber.d(e); { it == ifaceRegex } } - private val request = networkRequestBuilder().apply { + private val request = globalNetworkRequestBuilder().apply { removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt index c8629496..9f872c43 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/UpstreamMonitor.kt @@ -2,9 +2,6 @@ package be.mygod.vpnhotspot.net.monitor import android.content.SharedPreferences import android.net.LinkProperties -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import android.os.Build import be.mygod.vpnhotspot.App.Companion.app import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -23,13 +20,6 @@ abstract class UpstreamMonitor { } private var monitor = generateMonitor() - fun networkRequestBuilder() = NetworkRequest.Builder().apply { - if (Build.VERSION.SDK_INT == 23) { // workarounds for OEM bugs - removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL) - } - } - fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) } fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt index 03ef2575..880ba10b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt @@ -5,15 +5,16 @@ import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import be.mygod.vpnhotspot.util.Services +import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber object VpnMonitor : UpstreamMonitor() { - private val request = networkRequestBuilder() - .addTransportType(NetworkCapabilities.TRANSPORT_VPN) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) - .build() + private val request = globalNetworkRequestBuilder().apply { + addTransportType(NetworkCapabilities.TRANSPORT_VPN) + removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + }.build() private var registered = false private val available = HashMap() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 5b6d4049..6d24d6b2 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -4,9 +4,7 @@ import android.annotation.SuppressLint import android.annotation.TargetApi import android.content.* import android.content.res.Resources -import android.net.InetAddresses -import android.net.LinkProperties -import android.net.RouteInfo +import android.net.* import android.os.Build import android.os.RemoteException import android.system.ErrnoException @@ -238,6 +236,14 @@ fun InvocationHandler.callSuper(interfaceClass: Class<*>, proxy: Any, method: Me } } +fun globalNetworkRequestBuilder() = NetworkRequest.Builder().apply { + if (Build.VERSION.SDK_INT >= 31) setIncludeOtherUidNetworks(true) else if (Build.VERSION.SDK_INT == 23) { + // workarounds for OEM bugs + removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL) + } +} + @Suppress("FunctionName") fun if_nametoindex(ifname: String) = if (Build.VERSION.SDK_INT >= 26) { Os.if_nametoindex(ifname) From cb417c8c2340a6d9b63a7a4d32059d39f8956850 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 21:35:44 -0400 Subject: [PATCH 070/285] Monitor network list instead --- .../vpnhotspot/SettingsPreferenceFragment.kt | 21 ++--- ...ompleteEditTextPreferenceDialogFragment.kt | 42 ---------- ...CompleteNetworkPreferenceDialogFragment.kt | 78 +++++++++++++++++++ 3 files changed, 86 insertions(+), 55 deletions(-) delete mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/preference/AlwaysAutoCompleteEditTextPreferenceDialogFragment.kt create mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt index 5d3372b0..cef0e46a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt @@ -14,13 +14,12 @@ import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor import be.mygod.vpnhotspot.net.monitor.IpMonitor import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock -import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragment +import be.mygod.vpnhotspot.preference.AutoCompleteNetworkPreferenceDialogFragment import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore import be.mygod.vpnhotspot.preference.SummaryFallbackProvider import be.mygod.vpnhotspot.root.Dump import be.mygod.vpnhotspot.root.RootManager import be.mygod.vpnhotspot.util.Services -import be.mygod.vpnhotspot.util.allInterfaceNames import be.mygod.vpnhotspot.util.launchUrl import be.mygod.vpnhotspot.util.showAllowingStateLoss import be.mygod.vpnhotspot.widget.SmartSnackbar @@ -144,16 +143,12 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { } } - override fun onDisplayPreferenceDialog(preference: Preference) { - when (preference.key) { - UpstreamMonitor.KEY, FallbackUpstreamMonitor.KEY -> - AlwaysAutoCompleteEditTextPreferenceDialogFragment().apply { - setArguments(preference.key, Services.connectivity.allNetworks.mapNotNull { - Services.connectivity.getLinkProperties(it)?.allInterfaceNames - }.flatten().toTypedArray()) - setTargetFragment(this@SettingsPreferenceFragment, 0) - }.showAllowingStateLoss(parentFragmentManager, preference.key) - else -> super.onDisplayPreferenceDialog(preference) - } + override fun onDisplayPreferenceDialog(preference: Preference) = when (preference.key) { + UpstreamMonitor.KEY, FallbackUpstreamMonitor.KEY -> + AutoCompleteNetworkPreferenceDialogFragment().apply { + setArguments(preference.key) + setTargetFragment(this@SettingsPreferenceFragment, 0) + }.showAllowingStateLoss(parentFragmentManager, preference.key) + else -> super.onDisplayPreferenceDialog(preference) } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AlwaysAutoCompleteEditTextPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AlwaysAutoCompleteEditTextPreferenceDialogFragment.kt deleted file mode 100644 index c1fce475..00000000 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AlwaysAutoCompleteEditTextPreferenceDialogFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package be.mygod.vpnhotspot.preference - -import android.content.Context -import android.view.View -import android.view.ViewGroup -import android.widget.ArrayAdapter -import androidx.core.os.bundleOf -import androidx.preference.EditTextPreferenceDialogFragmentCompat -import be.mygod.vpnhotspot.R -import be.mygod.vpnhotspot.widget.AlwaysAutoCompleteEditText - -class AlwaysAutoCompleteEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { - companion object { - private const val ARG_SUGGESTIONS = "suggestions" - } - - fun setArguments(key: String, suggestions: Array) { - arguments = bundleOf(ARG_KEY to key, ARG_SUGGESTIONS to suggestions) - } - - private lateinit var editText: AlwaysAutoCompleteEditText - - override fun onCreateDialogView(context: Context) = super.onCreateDialogView(context).apply { - editText = AlwaysAutoCompleteEditText(context).apply { - id = android.R.id.edit - minHeight = resources.getDimensionPixelSize(R.dimen.touch_target_min) - } - val oldEditText = findViewById(android.R.id.edit)!! - val container = oldEditText.parent as ViewGroup - container.removeView(oldEditText) - container.addView(editText, oldEditText.layoutParams) - } - - override fun onBindDialogView(view: View) { - super.onBindDialogView(view) - editText.hint = (preference.summaryProvider as SummaryFallbackProvider).fallback - arguments?.getStringArray(ARG_SUGGESTIONS)?.let { suggestions -> - editText.setAdapter(ArrayAdapter(view.context, android.R.layout.select_dialog_item, suggestions)) - } - editText.clearFocus() // having focus is buggy currently - } -} diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt new file mode 100644 index 00000000..a2effe05 --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt @@ -0,0 +1,78 @@ +package be.mygod.vpnhotspot.preference + +import android.content.Context +import android.net.ConnectivityManager +import android.net.LinkProperties +import android.net.Network +import android.net.NetworkCapabilities +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.core.os.bundleOf +import androidx.preference.EditTextPreferenceDialogFragmentCompat +import be.mygod.vpnhotspot.R +import be.mygod.vpnhotspot.util.Services +import be.mygod.vpnhotspot.util.allInterfaceNames +import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder +import be.mygod.vpnhotspot.widget.AlwaysAutoCompleteEditText + +class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { + fun setArguments(key: String) { + arguments = bundleOf(ARG_KEY to key) + } + + private lateinit var editText: AlwaysAutoCompleteEditText + private lateinit var adapter: ArrayAdapter + private fun updateAdapter() { + adapter.clear() + adapter.addAll(interfaceNames.flatMap { it.value }) + } + + private val interfaceNames = mutableMapOf>() + private val callback = object : ConnectivityManager.NetworkCallback() { + override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) { + interfaceNames[network] = properties.allInterfaceNames + updateAdapter() + } + + override fun onLost(network: Network) { + interfaceNames.remove(network) + updateAdapter() + } + } + + override fun onCreateDialogView(context: Context) = super.onCreateDialogView(context).apply { + editText = AlwaysAutoCompleteEditText(context).apply { + id = android.R.id.edit + minHeight = resources.getDimensionPixelSize(R.dimen.touch_target_min) + } + val oldEditText = findViewById(android.R.id.edit)!! + val container = oldEditText.parent as ViewGroup + container.removeView(oldEditText) + container.addView(editText, oldEditText.layoutParams) + } + + override fun onBindDialogView(view: View) { + super.onBindDialogView(view) + editText.hint = (preference.summaryProvider as SummaryFallbackProvider).fallback + adapter = ArrayAdapter(view.context, android.R.layout.select_dialog_item) + editText.setAdapter(adapter) + editText.clearFocus() // having focus is buggy currently + } + + override fun onStart() { + super.onStart() + Services.connectivity.registerNetworkCallback(globalNetworkRequestBuilder().apply { + removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + }.build(), callback) + } + + override fun onStop() { + Services.connectivity.unregisterNetworkCallback(callback) + interfaceNames.clear() + updateAdapter() + super.onStop() + } +} From ad2a45597bf264f639d7db0ae14a0ef936c274e6 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 21:42:42 -0400 Subject: [PATCH 071/285] Band needs to be set --- .../be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 0f68d590..07588eed 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 @@ -210,7 +210,7 @@ class WifiApDialogFragment : AlertDialogFragment 0 } private fun populateFromConfiguration() { dialogView.ssid.setText(base.ssid) @@ -310,10 +311,7 @@ class WifiApDialogFragment : AlertDialogFragment { - setBridgedMode() - true - } + else -> setBridgedMode() } dialogView.bssidWrapper.error = null val bssidValid = dialogView.bssid.length() == 0 || try { From c7d53db7e3fc8b1ebd16af9b49e8e3cb76fbeb0f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 21:43:54 -0400 Subject: [PATCH 072/285] Fix missing 60G --- .../java/be/mygod/vpnhotspot/net/wifi/WifiApDialogFragment.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 07588eed..8b4b412c 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 @@ -213,7 +213,8 @@ class WifiApDialogFragment : AlertDialogFragment auto = 1 !is ChannelOption.Disabled -> ++set } From 9652804f9a5d1a21c113fe53b359e0ed39d5cd16 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 22:50:01 -0400 Subject: [PATCH 073/285] Fix bssid might be null --- .../be/mygod/vpnhotspot/manage/TetherManager.kt | 14 +++++++++----- .../be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 2b9ee1ff..4cb41497 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -272,15 +272,19 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), val channel = SoftApConfigurationCompat.frequencyToChannel(frequency) val bandwidth = SoftApInfo.channelWidthLookup(info.bandwidth, true) if (Build.VERSION.SDK_INT >= 31) { - var bssid = makeMacSpan(info.bssid.toString()) - info.apInstanceIdentifier?.let { // take the fast route if possible - bssid = if (bssid is String) "$bssid%$it" else SpannableStringBuilder(bssid).append("%$it") - } + val bssid = info.bssid.let { if (it == null) null else makeMacSpan(it.toString()) } + val bssidAp = info.apInstanceIdentifier?.let { + when (bssid) { + null -> it + is String -> "$bssid%$it" // take the fast route if possible + else -> SpannableStringBuilder(bssid).append("%$it") + } + } ?: bssid ?: "?" val timeout = info.autoShutdownTimeoutMillis parent.getText(if (timeout == 0L) { R.string.tethering_manage_wifi_info_timeout_disabled } else R.string.tethering_manage_wifi_info_timeout_enabled).format(locale, - frequency, channel, bandwidth, bssid, info.wifiStandard, + frequency, channel, bandwidth, bssidAp, info.wifiStandard, // http://unicode.org/cldr/trac/ticket/3407 DateUtils.formatElapsedTime(timeout / 1000)) } else parent.getText(R.string.tethering_manage_wifi_info).format(locale, diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt index ccec77cf..c218f0bf 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt @@ -30,7 +30,7 @@ value class SoftApInfo(val inner: Parcelable) { val frequency get() = getFrequency(inner) as Int val bandwidth get() = getBandwidth(inner) as Int @get:RequiresApi(31) - val bssid get() = getBssid(inner) as MacAddress + val bssid get() = getBssid(inner) as MacAddress? @get:RequiresApi(31) val wifiStandard get() = getWifiStandard(inner) as Int @get:RequiresApi(31) From 37df32880dbb6712aecd4082e81e639e9ba504c2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 22:58:04 -0400 Subject: [PATCH 074/285] More descriptive errors in p2p supplicant --- .../vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt index 00f22e56..d291ac87 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/P2pSupplicantConfiguration.kt @@ -75,7 +75,13 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) { if (matchedBssid.isEmpty()) { check(block.pskLine == null && block.psk == null) if (match.groups[5] != null) { - block.psk = match.groupValues[5].apply { check(length in 8..63) } + block.psk = match.groupValues[5].apply { + when (length) { + in 8..63 -> { } + 64 -> error("WPA-PSK hex not supported") + else -> error("Unknown length $length") + } + } } block.pskLine = block.size } else if (bssids.any { matchedBssid.equals(it, true) }) { From 0c36bcbade50ccc3a14ad26a76460da08d07305b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 24 Jul 2021 23:29:50 -0400 Subject: [PATCH 075/285] Prevent Binder stub swallowing Exceptions --- .../main/java/be/mygod/vpnhotspot/net/TetheringManager.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 56ac2303..932eecbd 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -66,7 +66,11 @@ object TetheringManager { } private object InPlaceExecutor : Executor { - override fun execute(command: Runnable) = command.run() + override fun execute(command: Runnable) = try { + command.run() + } catch (e: Exception) { + Timber.w(e) // prevent Binder stub swallowing the exception + } } /** From 1d6fe3def10cff25b5a63ca58864309f9805c1fa Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 00:27:50 -0400 Subject: [PATCH 076/285] Make InvocationHandler more robust --- .../be/mygod/vpnhotspot/net/TetherType.kt | 4 +- .../mygod/vpnhotspot/net/TetheringManager.kt | 53 ++++++---------- .../vpnhotspot/net/wifi/SoftApCapability.kt | 2 +- .../mygod/vpnhotspot/net/wifi/SoftApInfo.kt | 2 +- .../vpnhotspot/net/wifi/WifiApManager.kt | 62 +++++++------------ .../mygod/vpnhotspot/net/wifi/WifiClient.kt | 2 +- .../net/wifi/WifiP2pManagerHelper.kt | 7 +-- .../java/be/mygod/vpnhotspot/util/Utils.kt | 8 +++ 8 files changed, 58 insertions(+), 82 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt index 18b6ef72..aad28004 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt @@ -71,9 +71,9 @@ enum class TetherType(@DrawableRes val icon: Int) { } @RequiresApi(30) - override fun onTetherableInterfaceRegexpsChanged(args: Array?) = synchronized(this) { + override fun onTetherableInterfaceRegexpsChanged(reg: Any?) = synchronized(this) { if (requiresUpdate) return@synchronized - Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}") + Timber.i("onTetherableInterfaceRegexpsChanged: $reg") TetheringManager.unregisterTetheringEventCallback(this) requiresUpdate = true listener() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 932eecbd..dd9da97c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -251,13 +251,12 @@ object TetheringManager { val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply { dexCache(cacheDir) handler { proxy, method, args -> - if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args") @Suppress("NAME_SHADOWING") val callback = reference.get() - when (method.name) { - "onTetheringStarted" -> callback?.onTetheringStarted() - "onTetheringFailed" -> callback?.onTetheringFailed() - else -> ProxyBuilder.callSuper(proxy, method, args) + if (args.isEmpty()) when (method.name) { + "onTetheringStarted" -> return@handler callback?.onTetheringStarted() + "onTetheringFailed" -> return@handler callback?.onTetheringFailed() } + ProxyBuilder.callSuper(proxy, method, args) } }.build() startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler) @@ -279,15 +278,9 @@ object TetheringManager { arrayOf(interfaceStartTetheringCallback), object : InvocationHandler { override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @Suppress("NAME_SHADOWING") val callback = reference.get() - return when (val name = method.name) { - "onTetheringStarted" -> { - if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringStarted() - } - "onTetheringFailed" -> { - if (args?.size != 1) Timber.w("Unexpected args for $name: $args") - callback?.onTetheringFailed(args?.get(0) as Int) - } + return when { + method.matches("onTetheringStarted") -> callback?.onTetheringStarted() + method.matches1("onTetheringFailed") -> callback?.onTetheringFailed(args?.get(0) as Int) else -> callSuper(interfaceStartTetheringCallback, proxy, method, args) } } @@ -449,7 +442,7 @@ object TetheringManager { * *@param reg The new regular expressions. * @hide */ - fun onTetherableInterfaceRegexpsChanged(args: Array?) {} + fun onTetherableInterfaceRegexpsChanged(reg: Any?) {} /** * Called when there was a change in the list of tetherable interfaces. Tetherable @@ -545,40 +538,34 @@ object TetheringManager { override fun invoke(proxy: Any, method: Method, args: Array?): Any? { @Suppress("NAME_SHADOWING") val callback = reference.get() - val noArgs = args?.size ?: 0 - return when (val name = method.name) { - "onTetheringSupported" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + return when { + method.matches1("onTetheringSupported") -> { callback?.onTetheringSupported(args!![0] as Boolean) } - "onUpstreamChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1("onUpstreamChanged") -> { callback?.onUpstreamChanged(args!![0] as Network?) } - "onTetherableInterfaceRegexpsChanged" -> { - if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args) + method.name == "onTetherableInterfaceRegexpsChanged" && + method.parameters.singleOrNull()?.type?.name == + "android.net.TetheringManager\$TetheringInterfaceRegexps" -> { + if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args!!.single()) regexpsSent = true } - "onTetherableInterfacesChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onTetherableInterfacesChanged") -> { @Suppress("UNCHECKED_CAST") callback?.onTetherableInterfacesChanged(args!![0] as List) } - "onTetheredInterfacesChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onTetheredInterfacesChanged") -> { @Suppress("UNCHECKED_CAST") callback?.onTetheredInterfacesChanged(args!![0] as List) } - "onError" -> { - if (noArgs != 2) Timber.w("Unexpected args for $name: $args") + method.matches2("onError") -> { callback?.onError(args!![0] as String, args[1] as Int) } - "onClientsChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1>("onClientsChanged") -> { callback?.onClientsChanged(args!![0] as Collection<*>) } - "onOffloadStatusChanged" -> { - if (noArgs != 1) Timber.w("Unexpected args for $name: $args") + method.matches1("onOffloadStatusChanged") -> { callback?.onOffloadStatusChanged(args!![0] as Int) } else -> callSuper(interfaceTetheringEventCallback, proxy, method, args) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt index 24432405..dbed4cc0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApCapability.kt @@ -8,7 +8,7 @@ import be.mygod.vpnhotspot.util.LongConstantLookup @RequiresApi(30) value class SoftApCapability(val inner: Parcelable) { companion object { - private val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") } + val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") } private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") } private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) } @get:RequiresApi(31) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt index c218f0bf..167534d5 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/SoftApInfo.kt @@ -12,7 +12,7 @@ import timber.log.Timber @RequiresApi(30) value class SoftApInfo(val inner: Parcelable) { companion object { - private val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") } + val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") } private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") } private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") } @get:RequiresApi(31) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 0a83397f..46438958 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -10,13 +10,9 @@ import android.os.Build import android.os.Handler import android.os.Parcelable import androidx.annotation.RequiresApi -import androidx.core.os.BuildCompat import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat -import be.mygod.vpnhotspot.util.ConstantLookup -import be.mygod.vpnhotspot.util.Services -import be.mygod.vpnhotspot.util.callSuper -import be.mygod.vpnhotspot.util.findIdentifier +import be.mygod.vpnhotspot.util.* import timber.log.Timber import java.lang.reflect.InvocationHandler import java.lang.reflect.Method @@ -260,55 +256,41 @@ object WifiApManager { } else invokeActual(proxy, method, args) private fun invokeActual(proxy: Any, method: Method, args: Array?): Any? { - val noArgs = args?.size ?: 0 - return when (val name = method.name) { - "onStateChanged" -> { - if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}") + return when { + method.matches2("onStateChanged") -> { callback.onStateChanged(args!![0] as Int, args[1] as Int) } - "onNumClientsChanged" -> @Suppress("DEPRECATION") { + method.matches1("onNumClientsChanged") -> @Suppress("DEPRECATION") { if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged")) - if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") callback.onNumClientsChanged(args!![0] as Int) } - "onConnectedClientsChanged" -> @TargetApi(30) { + method.matches1>("onConnectedClientsChanged") -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged")) @Suppress("UNCHECKED_CAST") - when (noArgs) { - 1 -> callback.onConnectedClientsChanged(args!![0] as List) - 2 -> null // we use the old method which returns all clients in one call - else -> { - Timber.w("Unexpected args for $name: ${args?.contentToString()}") - null - } - } + callback.onConnectedClientsChanged(args!![0] as List) } - "onInfoChanged" -> @TargetApi(30) { - if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") + method.matches1>("onInfoChanged") -> @TargetApi(31) { + if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) + @Suppress("UNCHECKED_CAST") + callback.onInfoChanged(args!![0] as List) + } + method.matches("onInfoChanged", SoftApInfo.clazz) -> @TargetApi(30) { + when (Build.VERSION.SDK_INT) { + 30 -> { } + in 31..Int.MAX_VALUE -> return null // ignore old version calls + else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) + } val arg = args!![0] - if (arg is List<*>) { - if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+")) - @Suppress("UNCHECKED_CAST") - callback.onInfoChanged(arg as List) - } else { - when (Build.VERSION.SDK_INT) { - 30 -> { } - in 31..Int.MAX_VALUE -> return null // ignore old version calls - else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) - } - val info = SoftApInfo(arg as Parcelable) - callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID - if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) - } + val info = SoftApInfo(arg as Parcelable) + callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID + if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) } - "onCapabilityChanged" -> @TargetApi(30) { + method.matches("onCapabilityChanged", SoftApCapability.clazz) -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged")) - if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}") callback.onCapabilityChanged(args!![0] as Parcelable) } - "onBlockedClientConnecting" -> @TargetApi(30) { + method.matches("onBlockedClientConnecting", WifiClient.clazz, Int::class.java) -> @TargetApi(30) { if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting")) - if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}") callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int) } else -> callSuper(interfaceSoftApCallback, proxy, method, args) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt index 57edc1a3..b106db9d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiClient.kt @@ -11,7 +11,7 @@ import timber.log.Timber @RequiresApi(30) value class WifiClient(val inner: Parcelable) { companion object { - private val clazz by lazy { Class.forName("android.net.wifi.WifiClient") } + val clazz by lazy { Class.forName("android.net.wifi.WifiClient") } private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") } @get:RequiresApi(31) private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) } 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 2f26d7a3..f95c87d9 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 @@ -9,8 +9,8 @@ import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.util.callSuper +import be.mygod.vpnhotspot.util.matches1 import kotlinx.coroutines.CompletableDeferred -import timber.log.Timber import java.lang.reflect.InvocationHandler import java.lang.reflect.Method import java.lang.reflect.Proxy @@ -117,9 +117,8 @@ object WifiP2pManagerHelper { val result = CompletableDeferred>() requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler { - override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when (method.name) { - "onPersistentGroupInfoAvailable" -> { - if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) + override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when { + method.matches1>("onPersistentGroupInfoAvailable") -> { @Suppress("UNCHECKED_CAST") result.complete(getGroupList(args!![0]) as Collection) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 6d24d6b2..49cb547f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -53,6 +53,14 @@ fun Long.toPluralInt(): Int { return (this % 1000000000).toInt() + 1000000000 } +@RequiresApi(26) +fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size && + (0 until parameterCount).all { i -> parameters[i].type == classes[i] } +@RequiresApi(26) +inline fun Method.matches1(name: String) = matches(name, T::class.java) +@RequiresApi(26) +inline fun Method.matches2(name: String) = matches(name, T0::class.java, T1::class.java) + fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) { try { unregisterReceiver(receiver) From b2a65915fd44f23db946c5edb89f56d911078871 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 00:37:05 -0400 Subject: [PATCH 077/285] Revert hide notification explicitly --- mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index cabea3aa..caacd93b 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -64,7 +64,7 @@ object ServiceNotification { fun stopForeground(service: Service) = synchronized(this) { val shutdown = deviceCountsMap.remove(service) != null && deviceCountsMap.isEmpty() service.stopForeground(shutdown) - if (shutdown) manager.cancel(NOTIFICATION_ID) else manager.notify(NOTIFICATION_ID, buildNotification(service)) + if (!shutdown) manager.notify(NOTIFICATION_ID, buildNotification(service)) } fun updateNotificationChannels() { From 00b869397dc2d028706a1a6fe5d323302a1e868b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 01:25:30 -0400 Subject: [PATCH 078/285] Fix Kotlin stupid bug (KT-47881) --- .../java/be/mygod/vpnhotspot/net/TetheringManager.kt | 10 ++++++---- .../java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt | 4 ++-- mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt | 2 -- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index dd9da97c..8fc28bec 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -280,7 +280,9 @@ object TetheringManager { @Suppress("NAME_SHADOWING") val callback = reference.get() return when { method.matches("onTetheringStarted") -> callback?.onTetheringStarted() - method.matches1("onTetheringFailed") -> callback?.onTetheringFailed(args?.get(0) as Int) + method.matches("onTetheringFailed", Integer.TYPE) -> { + callback?.onTetheringFailed(args?.get(0) as Int) + } else -> callSuper(interfaceStartTetheringCallback, proxy, method, args) } } @@ -539,7 +541,7 @@ object TetheringManager { @Suppress("NAME_SHADOWING") val callback = reference.get() return when { - method.matches1("onTetheringSupported") -> { + method.matches("onTetheringSupported", Boolean::class.java) -> { callback?.onTetheringSupported(args!![0] as Boolean) } method.matches1("onUpstreamChanged") -> { @@ -559,13 +561,13 @@ object TetheringManager { @Suppress("UNCHECKED_CAST") callback?.onTetheredInterfacesChanged(args!![0] as List) } - method.matches2("onError") -> { + method.matches("onError", String::class.java, Integer.TYPE) -> { callback?.onError(args!![0] as String, args[1] as Int) } method.matches1>("onClientsChanged") -> { callback?.onClientsChanged(args!![0] as Collection<*>) } - method.matches1("onOffloadStatusChanged") -> { + method.matches("onOffloadStatusChanged", Integer.TYPE) -> { callback?.onOffloadStatusChanged(args!![0] as Int) } else -> callSuper(interfaceTetheringEventCallback, proxy, method, args) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 46438958..2afad6a0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -257,10 +257,10 @@ object WifiApManager { private fun invokeActual(proxy: Any, method: Method, args: Array?): Any? { return when { - method.matches2("onStateChanged") -> { + method.matches("onStateChanged", Integer.TYPE, Integer.TYPE) -> { callback.onStateChanged(args!![0] as Int, args[1] as Int) } - method.matches1("onNumClientsChanged") -> @Suppress("DEPRECATION") { + method.matches("onNumClientsChanged", Integer.TYPE) -> @Suppress("DEPRECATION") { if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged")) callback.onNumClientsChanged(args!![0] as Int) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 49cb547f..51aad9ee 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -58,8 +58,6 @@ fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name & (0 until parameterCount).all { i -> parameters[i].type == classes[i] } @RequiresApi(26) inline fun Method.matches1(name: String) = matches(name, T::class.java) -@RequiresApi(26) -inline fun Method.matches2(name: String) = matches(name, T0::class.java, T1::class.java) fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) { try { From 6ed3044dfcb884d71f88b6f33c3b01a2bebebe62 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 04:13:10 -0400 Subject: [PATCH 079/285] Fix class not found on lower Android versions --- .../be/mygod/vpnhotspot/net/wifi/WifiApManager.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt index 2afad6a0..d99e13f8 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiApManager.kt @@ -274,23 +274,18 @@ object WifiApManager { @Suppress("UNCHECKED_CAST") callback.onInfoChanged(args!![0] as List) } - method.matches("onInfoChanged", SoftApInfo.clazz) -> @TargetApi(30) { - when (Build.VERSION.SDK_INT) { - 30 -> { } - in 31..Int.MAX_VALUE -> return null // ignore old version calls - else -> Timber.w(Exception("Unexpected onInfoChanged API 30")) - } + Build.VERSION.SDK_INT >= 30 && method.matches("onInfoChanged", SoftApInfo.clazz) -> { + if (Build.VERSION.SDK_INT >= 31) return null // ignore old version calls val arg = args!![0] val info = SoftApInfo(arg as Parcelable) callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg)) } - method.matches("onCapabilityChanged", SoftApCapability.clazz) -> @TargetApi(30) { - if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged")) + Build.VERSION.SDK_INT >= 30 && method.matches("onCapabilityChanged", SoftApCapability.clazz) -> { callback.onCapabilityChanged(args!![0] as Parcelable) } - method.matches("onBlockedClientConnecting", WifiClient.clazz, Int::class.java) -> @TargetApi(30) { - if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting")) + Build.VERSION.SDK_INT >= 30 && method.matches("onBlockedClientConnecting", WifiClient.clazz, + Int::class.java) -> { callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int) } else -> callSuper(interfaceSoftApCallback, proxy, method, args) From bdaa8c3f51b5092a65bd4add3c4e0315cce3023b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 14:20:21 -0400 Subject: [PATCH 080/285] v2.12.5 --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 80bc2356..73d36734 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 275 - versionName = "2.12.4" + versionCode = 276 + versionName = "2.12.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From ed09f747cea2a3294edb79ec1320f95aebbb918e Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 22:38:17 -0400 Subject: [PATCH 081/285] Fix PersistentGroupInfoListener --- .../be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt | 9 ++++----- mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt | 9 ++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) 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 f95c87d9..d4749c8e 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 @@ -9,7 +9,7 @@ import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.MacAddressCompat import be.mygod.vpnhotspot.util.callSuper -import be.mygod.vpnhotspot.util.matches1 +import be.mygod.vpnhotspot.util.matchesCompat import kotlinx.coroutines.CompletableDeferred import java.lang.reflect.InvocationHandler import java.lang.reflect.Method @@ -99,9 +99,8 @@ object WifiP2pManagerHelper { private val interfacePersistentGroupInfoListener by lazy { Class.forName("android.net.wifi.p2p.WifiP2pManager\$PersistentGroupInfoListener") } - private val getGroupList by lazy { - Class.forName("android.net.wifi.p2p.WifiP2pGroupList").getDeclaredMethod("getGroupList") - } + private val classWifiP2pGroupList by lazy { Class.forName("android.net.wifi.p2p.WifiP2pGroupList") } + private val getGroupList by lazy { classWifiP2pGroupList.getDeclaredMethod("getGroupList") } private val requestPersistentGroupInfo by lazy { WifiP2pManager::class.java.getDeclaredMethod("requestPersistentGroupInfo", WifiP2pManager.Channel::class.java, interfacePersistentGroupInfoListener) @@ -118,7 +117,7 @@ object WifiP2pManagerHelper { requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader, arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler { override fun invoke(proxy: Any, method: Method, args: Array?): Any? = when { - method.matches1>("onPersistentGroupInfoAvailable") -> { + method.matchesCompat("onPersistentGroupInfoAvailable", args, classWifiP2pGroupList) -> { @Suppress("UNCHECKED_CAST") result.complete(getGroupList(args!![0]) as Collection) } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt index 51aad9ee..75d27f73 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Utils.kt @@ -55,10 +55,17 @@ fun Long.toPluralInt(): Int { @RequiresApi(26) fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size && - (0 until parameterCount).all { i -> parameters[i].type == classes[i] } + classes.indices.all { i -> parameters[i].type == classes[i] } @RequiresApi(26) inline fun Method.matches1(name: String) = matches(name, T::class.java) +fun Method.matchesCompat(name: String, args: Array?, vararg classes: Class<*>) = + if (Build.VERSION.SDK_INT < 26) { + this.name == name && args?.size ?: 0 == classes.size && classes.indices.all { i -> + args!![i]?.let { classes[i].isInstance(it) } != false + } + } else matches(name, *classes) + fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) { try { unregisterReceiver(receiver) From 5f6e33c85280e35cea46aa1e0a6a5d8ead0638e3 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 25 Jul 2021 22:51:50 -0400 Subject: [PATCH 082/285] Fix notification again --- .../src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt index caacd93b..740b170e 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/ServiceNotification.kt @@ -62,7 +62,8 @@ object ServiceNotification { } } fun stopForeground(service: Service) = synchronized(this) { - val shutdown = deviceCountsMap.remove(service) != null && deviceCountsMap.isEmpty() + deviceCountsMap.remove(service) ?: return@synchronized + val shutdown = deviceCountsMap.isEmpty() service.stopForeground(shutdown) if (!shutdown) manager.notify(NOTIFICATION_ID, buildNotification(service)) } From 018682e2f5d38be507083ad8afd7310a8c10d3d8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 27 Jul 2021 14:53:23 -0400 Subject: [PATCH 083/285] Remove Bluetooth tethering if unsupported --- .../vpnhotspot/manage/BluetoothTethering.kt | 12 ++++-------- .../mygod/vpnhotspot/manage/TetherManager.kt | 6 ++++-- .../vpnhotspot/manage/TetheringFragment.kt | 18 +++++++++++++----- .../vpnhotspot/manage/TetheringTileService.kt | 6 +++++- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index bbb9e823..ac87da09 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -3,7 +3,6 @@ package be.mygod.vpnhotspot.manage import android.annotation.SuppressLint import android.annotation.TargetApi import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.BroadcastReceiver import android.content.Context @@ -11,7 +10,6 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import androidx.annotation.RequiresApi -import androidx.core.content.getSystemService import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.util.broadcastReceiver @@ -20,7 +18,7 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar import timber.log.Timber import java.lang.reflect.InvocationTargetException -class BluetoothTethering(context: Context, val stateListener: () -> Unit) : +class BluetoothTethering(context: Context, private val adapter: BluetoothAdapter, val stateListener: () -> Unit) : BluetoothProfile.ServiceListener, AutoCloseable { companion object : BroadcastReceiver() { /** @@ -30,7 +28,6 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") } private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") } - private val adapter = app.getSystemService()?.adapter private val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean private fun registerBluetoothStateListener(receiver: BroadcastReceiver) = @@ -67,7 +64,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : val pan = pan ?: return null if (!connected) return null activeFailureCause = null - val on = adapter?.state == BluetoothAdapter.STATE_ON && try { + val on = adapter.state == BluetoothAdapter.STATE_ON && try { pan.isTetheringOn } catch (e: InvocationTargetException) { activeFailureCause = e.cause ?: e @@ -84,7 +81,6 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val receiver = broadcastReceiver { _, _ -> stateListener() } fun ensureInit(context: Context) { - val adapter = adapter ?: return activeFailureCause = null if (!proxyCreated) try { check(adapter.getProfileProxy(context, this, PAN)) @@ -116,7 +112,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : @RequiresApi(24) fun start(callback: TetheringManager.StartTetheringCallback) { if (pendingCallback == null) try { - if (adapter?.state == BluetoothAdapter.STATE_OFF) { + if (adapter.state == BluetoothAdapter.STATE_OFF) { registerBluetoothStateListener(BluetoothTethering) pendingCallback = callback adapter.enable() @@ -134,6 +130,6 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : override fun close() { app.unregisterReceiver(receiver) - adapter!!.closeProfileProxy(PAN, pan) + adapter.closeProfileProxy(PAN, pan) } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 4cb41497..685ee034 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -2,6 +2,7 @@ package be.mygod.vpnhotspot.manage import android.Manifest import android.annotation.TargetApi +import android.bluetooth.BluetoothAdapter import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -306,8 +307,9 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException) } @RequiresApi(24) - class Bluetooth(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver { - private val tethering = BluetoothTethering(parent.requireContext()) { data.notifyChange() } + class Bluetooth(parent: TetheringFragment, adapter: BluetoothAdapter) : + TetherManager(parent), DefaultLifecycleObserver { + private val tethering = BluetoothTethering(parent.requireContext(), adapter) { data.notifyChange() } init { parent.viewLifecycleOwner.lifecycle.addObserver(this) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index b799f9f8..6253db9f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.manage import android.annotation.TargetApi +import android.bluetooth.BluetoothManager import android.content.* import android.os.Build import android.os.Bundle @@ -15,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DefaultItemAnimator @@ -49,12 +51,18 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick @get:RequiresApi(26) internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) } @get:RequiresApi(24) - internal val bluetoothManager by lazy @TargetApi(24) { TetherManager.Bluetooth(this@TetheringFragment) } + internal val bluetoothManager by lazy { + if (Build.VERSION.SDK_INT >= 24) requireContext().getSystemService()?.adapter?.let { + TetherManager.Bluetooth(this@TetheringFragment, it) + } else null + } @get:RequiresApi(24) private val tetherManagers by lazy @TargetApi(24) { - listOf(TetherManager.Wifi(this@TetheringFragment), - TetherManager.Usb(this@TetheringFragment), - bluetoothManager) + listOfNotNull( + TetherManager.Wifi(this@TetheringFragment), + TetherManager.Usb(this@TetheringFragment), + bluetoothManager, + ) } @get:RequiresApi(30) private val tetherManagers30 by lazy @TargetApi(30) { @@ -139,7 +147,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick } @RequiresApi(31) val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - if (granted) adapter.bluetoothManager.ensureInit(requireContext()) + if (granted) adapter.bluetoothManager!!.ensureInit(requireContext()) } var ifaceLookup: Map = emptyMap() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index 4efd8174..18442497 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -1,5 +1,6 @@ package be.mygod.vpnhotspot.manage +import android.bluetooth.BluetoothManager import android.content.ComponentName import android.content.Context import android.content.Intent @@ -11,6 +12,7 @@ import android.service.quicksettings.Tile import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.TetheringService import be.mygod.vpnhotspot.net.TetherType @@ -158,7 +160,9 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin } override fun onStartListening() { - tethering = BluetoothTethering(this) { updateTile() } + tethering = getSystemService()?.adapter?.let { + BluetoothTethering(this, it) { updateTile() } + } super.onStartListening() } override fun onStopListening() { From b9b26aa81bff42c261add5537ee6613641ded95a Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 28 Jul 2021 16:50:05 -0400 Subject: [PATCH 084/285] v2.12.6 --- build.gradle.kts | 2 +- mobile/build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5a06bcdf..f80fd139 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ buildscript { dependencies { classpath(kotlin("gradle-plugin", "1.5.21")) - classpath("com.android.tools.build:gradle:7.0.0-rc01") + classpath("com.android.tools.build:gradle:7.0.0") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") classpath("com.google.gms:google-services:4.3.8") diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 73d36734..57c710cd 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -24,8 +24,8 @@ android { minSdk = 21 this.targetSdk = targetSdk resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW")) - versionCode = 276 - versionName = "2.12.5" + versionCode = 277 + versionName = "2.12.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions.annotationProcessorOptions.arguments.apply { put("room.expandProjection", "true") From 19eac93bc9b337831bf23d1d8e16087d1b3df364 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 29 Jul 2021 11:11:03 +0800 Subject: [PATCH 085/285] Update to Java 11 --- mobile/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 57c710cd..005b6a3f 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } android { - val javaVersion = JavaVersion.VERSION_1_8 + val javaVersion = JavaVersion.VERSION_11 val targetSdk = 29 buildToolsVersion = "31.0.0" compileOptions { From 605a3c5244bc5281edd9e1166a7df1529020195b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 31 Jul 2021 19:58:27 -0400 Subject: [PATCH 086/285] Fix updating from wrong thread --- .../AutoCompleteNetworkPreferenceDialogFragment.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt index a2effe05..c1ba8e30 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt @@ -9,6 +9,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreferenceDialogFragmentCompat import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.util.Services @@ -32,12 +33,12 @@ class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFrag private val callback = object : ConnectivityManager.NetworkCallback() { override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) { interfaceNames[network] = properties.allInterfaceNames - updateAdapter() + lifecycleScope.launchWhenStarted { updateAdapter() } } override fun onLost(network: Network) { interfaceNames.remove(network) - updateAdapter() + lifecycleScope.launchWhenStarted { updateAdapter() } } } From a15e9d303e6ea5504b92dd8121321fdb16dd9a48 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 00:21:25 -0400 Subject: [PATCH 087/285] Update dependencies --- README.md | 2 +- build.gradle.kts | 4 ++-- mobile/build.gradle.kts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 82a59b9e..46216d46 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ _a.k.a. things that can go wrong if this app doesn't work._ This is a list of stuff that might impact this app's functionality if unavailable. This is only meant to be an index. You can read more in the source code. -API restrictions are updated up to [SHA-256 checksum `156715dfa705a048926dca876d731d72604df32e8bcac055af32866b50bc2cc8`](https://dl.google.com/developers/android/sc/non-sdk/hiddenapi-flags.csv). +API restrictions are updated up to [SHA-256 checksum `d594d905147a3b74af0f9e026939e56856eeccce1995abff3634729be23deeed`](https://dl.google.com/developers/android/sc/non-sdk/hiddenapi-flags.csv). Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used) diff --git a/build.gradle.kts b/build.gradle.kts index f80fd139..12c6e34d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,10 +10,10 @@ buildscript { dependencies { classpath(kotlin("gradle-plugin", "1.5.21")) - classpath("com.android.tools.build:gradle:7.0.0") + classpath("com.android.tools.build:gradle:7.0.1") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") - classpath("com.google.gms:google-services:4.3.8") + classpath("com.google.gms:google-services:4.3.10") } } diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index 005b6a3f..d2f02c65 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -83,11 +83,11 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.android.billingclient:billing-ktx:4.0.0") implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") - implementation("com.google.android.material:material:1.5.0-alpha01") - implementation("com.google.firebase:firebase-analytics-ktx:19.0.0") - implementation("com.google.firebase:firebase-crashlytics:18.2.0") + implementation("com.google.android.material:material:1.5.0-alpha02") + implementation("com.google.firebase:firebase-analytics-ktx:19.0.1") + implementation("com.google.firebase:firebase-crashlytics:18.2.1") implementation("com.google.zxing:core:3.4.1") - implementation("com.jakewharton.timber:timber:4.7.1") + implementation("com.jakewharton.timber:timber:5.0.1") implementation("com.linkedin.dexmaker:dexmaker:2.28.1") implementation("com.takisoft.preferencex:preferencex-simplemenu:1.1.0") implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.4") From b52d0e8240fec9dfbaedd8170f03002fd2c2740b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 00:38:11 -0400 Subject: [PATCH 088/285] Fix bugs in TrafficRecorder Addresses #220. --- .../java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt index 52e3dbf8..422fce23 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt @@ -130,6 +130,7 @@ object TrafficRecorder { } fun update(timeout: Boolean = false) { synchronized(this) { + unscheduleUpdateLocked() if (records.isEmpty()) return val timestamp = System.currentTimeMillis() if (!timeout && timestamp - lastUpdate <= 100) return @@ -141,7 +142,6 @@ object TrafficRecorder { SmartSnackbar.make(e).show() } lastUpdate = timestamp - updateJob = null scheduleUpdateLocked() } } From 916b4a992a2ddb6a171f3ce2e3f15bee4a5aba87 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 29 Aug 2021 11:35:11 +0800 Subject: [PATCH 089/285] Update Kotlin --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 12c6e34d..3f47a83f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath(kotlin("gradle-plugin", "1.5.21")) + classpath(kotlin("gradle-plugin", "1.5.30")) classpath("com.android.tools.build:gradle:7.0.1") classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.android.gms:oss-licenses-plugin:0.10.4") From c9fd19985c0e330e83d15cd2f0999e568e9408b1 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 29 Aug 2021 11:38:20 +0800 Subject: [PATCH 090/285] Refine code style --- .../java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt index 422fce23..77e6501a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt @@ -107,9 +107,9 @@ object TrafficRecorder { record.sentBytes = columns[1].toLong() } } - if (oldRecord.id != null) { + oldRecord.id?.let { oldId -> check(records.put(key, record) == oldRecord) - oldRecords[oldRecord.id!!] = oldRecord + oldRecords[oldId] = oldRecord } } else -> check(false) From fb8538ecbedc7292e33f2a756cc4cea0248057d9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 02:20:02 -0400 Subject: [PATCH 091/285] Fix race conditions for cancellables --- mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt index 47559b39..95ac2bfb 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt @@ -400,6 +400,7 @@ class RootServer { CoroutineScope(Dispatchers.Main.immediate + job) } val callbackWorker = newSingleThreadContext("callbackWorker") + // access to cancellables shall be wrapped in defaultWorker val cancellables = LongSparseArray<() -> Unit>() // thread safety: usage of output should be guarded by callbackWorker @@ -424,7 +425,7 @@ class RootServer { val callback = counter Logger.me.d("Received #$callback: $command") when (command) { - is CancelCommand -> cancellables[command.index]?.invoke() + is CancelCommand -> defaultWorker.launch { cancellables[command.index]?.invoke() } is RootCommandOneWay -> defaultWorker.launch { try { command.execute() @@ -434,8 +435,8 @@ class RootServer { } is RootCommand<*> -> { val commandJob = Job() - cancellables.append(callback) { commandJob.cancel() } defaultWorker.launch(commandJob) { + cancellables.append(callback) { commandJob.cancel() } val result = try { val result = command.execute(); { output.pushResult(callback, result) } From f50d579fd37f23c215f6a83e96d9f200de921992 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 02:36:30 -0400 Subject: [PATCH 092/285] Wrap race condition in CancellationException --- .../main/java/be/mygod/librootkotlinx/RootServer.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt index 95ac2bfb..7a8f624f 100644 --- a/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt +++ b/mobile/src/main/java/be/mygod/librootkotlinx/RootServer.kt @@ -231,8 +231,12 @@ class RootServer { * Caller should check for active. */ private fun sendLocked(command: Parcelable) { - output.writeParcelable(command) - output.flush() + try { + output.writeParcelable(command) + output.flush() + } catch (e: IOException) { + if (e.isEBADF) throw CancellationException().initCause(e) else throw e + } Logger.me.d("Sent #$counter: $command") counter++ } @@ -300,8 +304,9 @@ class RootServer { sendLocked(Shutdown()) output.close() process.outputStream.close() + } catch (_: CancellationException) { } catch (e: IOException) { - if (!e.isEBADF) Logger.me.w("send Shutdown failed", e) + Logger.me.w("send Shutdown failed", e) } Logger.me.d("Client closed") } From abf6913de4461d7f4a35e4687c4f5d8ae11b6e30 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 02:45:13 -0400 Subject: [PATCH 093/285] Check blocked/allowed lists for duplicates --- .../net/wifi/WifiApDialogFragment.kt | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) 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 8b4b412c..de86fd88 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 @@ -331,24 +331,27 @@ class WifiApDialogFragment : AlertDialogFragment= 30) { + val (blockedList, blockedListError) = try { + (dialogView.blockedList.text ?: "").split(nonMacChars) + .filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }.toSet() to null + } catch (e: IllegalArgumentException) { + null to e.readableMessage + } + dialogView.blockedListWrapper.error = blockedListError + val allowedListError = try { + (dialogView.allowedList.text ?: "").split(nonMacChars).filter { it.isNotEmpty() }.forEach { + val mac = MacAddressCompat.fromString(it).toPlatform() + require(blockedList?.contains(mac) != true) { "A MAC address exists in both client lists" } + } + null + } catch (e: IllegalArgumentException) { + e.readableMessage + } + dialogView.allowedListWrapper.error = allowedListError + blockedListError == null && allowedListError == null + } else true + val canCopy = timeoutError == null && bssidValid && maxClientError == null && listsNoError (dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = ssidLength in 1..32 && passwordValid && isBandValid && canCopy dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy From e9081e67d7d0dc95b470ed6701b847fc50740b09 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 20:19:49 -0400 Subject: [PATCH 094/285] Refine code style --- .../vpnhotspot/LocalOnlyHotspotService.kt | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 95ead806..95fcfd83 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -65,32 +65,31 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope { try { Services.wifi.startLocalOnlyHotspot(object : WifiManager.LocalOnlyHotspotCallback() { override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) { - if (reservation == null) onFailed(-2) else { - this@LocalOnlyHotspotService.reservation = reservation - val configuration = binder.configuration!! - if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) { - timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis, - coroutineContext) { reservation.close() } + if (reservation == null) return onFailed(-2) + this@LocalOnlyHotspotService.reservation = reservation + val configuration = binder.configuration!! + if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) { + timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis, coroutineContext) { + reservation.close() } - // based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160 - val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!! - val apState = sticky.wifiApState - val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME) - if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) { - if (apState == WifiApManager.WIFI_AP_STATE_FAILED) { - SmartSnackbar.make(getString(R.string.tethering_temp_hotspot_failure, - WifiApManager.failureReasonLookup(sticky.getIntExtra( - WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0)))).show() - } - return stopService() - } - binder.iface = iface - launch { - check(routingManager == null) - routingManager = RoutingManager.LocalOnly( - this@LocalOnlyHotspotService, iface).apply { start() } - IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService) + } + // based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160 + val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!! + val apState = sticky.wifiApState + val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME) + if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) { + if (apState == WifiApManager.WIFI_AP_STATE_FAILED) { + SmartSnackbar.make(getString(R.string.tethering_temp_hotspot_failure, + WifiApManager.failureReasonLookup(sticky.getIntExtra( + WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0)))).show() } + return stopService() + } + binder.iface = iface + launch { + check(routingManager == null) + routingManager = RoutingManager.LocalOnly(this@LocalOnlyHotspotService, iface).apply { start() } + IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService) } } From 2856ad20804a5752287d00ce1cca311b96f3bab7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Aug 2021 23:33:38 -0400 Subject: [PATCH 095/285] Refine channel error checking and allow multi-channel auto --- .../vpnhotspot/manage/RepeaterManager.kt | 2 +- .../net/wifi/SoftApConfigurationCompat.kt | 54 ++++++++------- .../net/wifi/WifiApDialogFragment.kt | 68 +++++++++---------- mobile/src/main/res/layout/dialog_wifi_ap.xml | 22 ++++-- 4 files changed, 82 insertions(+), 64 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 796c8282..37522bf6 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/RepeaterManager.kt @@ -226,7 +226,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic return null } private suspend fun updateConfiguration(config: SoftApConfigurationCompat) { - val (band, channel) = config.requireSingleBand() + val (band, channel) = SoftApConfigurationCompat.requireSingleBand(config.channels) if (RepeaterService.safeMode) { RepeaterService.networkName = config.ssid RepeaterService.deviceAddress = config.bssid 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 fc4ee751..d9ba375e 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 @@ -333,6 +333,33 @@ data class SoftApConfigurationCompat( Build.VERSION.SDK_INT < 31 || isUserConfiguration(this) as Boolean, this, ) + + /** + * Only single band/channel can be supplied on API 23-30 + */ + fun requireSingleBand(channels: SparseIntArray): Pair { + require(channels.size() == 1) { "Unsupported number of bands configured" } + return channels.keyAt(0) to channels.valueAt(0) + } + fun optimizeChannels(channels: SparseIntArray) = SparseIntArray(channels.size()).apply { + var setBand = 0 + repeat(channels.size()) { i -> if (channels.valueAt(i) == 0) setBand = setBand or channels.keyAt(i) } + if (setBand != 0) append(setBand, 0) // merge all bands into one + repeat(channels.size()) { i -> + val band = channels.keyAt(i) + if (band and setBand == 0) put(band, channels.valueAt(i)) + } + } + + @RequiresApi(30) + private fun setChannelsCompat(builder: Any, channels: SparseIntArray) = if (Build.VERSION.SDK_INT < 31) { + val (band, channel) = requireSingleBand(channels) + if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band) + } else setChannels(builder, channels) + @get:RequiresApi(30) + private val staticBuilder by lazy { classBuilder.newInstance() } + @RequiresApi(30) + fun testPlatformValidity(channels: SparseIntArray) = setChannels(staticBuilder, channels) } @Suppress("DEPRECATION") @@ -342,13 +369,6 @@ data class SoftApConfigurationCompat( bssidAddr = value?.addr } - /** - * 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 repeat(channels.size()) { i -> @@ -367,17 +387,6 @@ data class SoftApConfigurationCompat( }, channel) } } - fun optimizeChannels(channels: SparseIntArray = this.channels) { - this.channels = SparseIntArray(channels.size()).apply { - var setBand = 0 - repeat(channels.size()) { i -> if (channels.valueAt(i) == 0) setBand = setBand or channels.keyAt(i) } - if (setBand != 0) append(setBand, 0) // merge all bands into one - repeat(channels.size()) { i -> - val band = channels.keyAt(i) - if (band and setBand == 0) put(band, channels.valueAt(i)) - } - } - } fun setMacRandomizationEnabled(enabled: Boolean) { macRandomizationSetting = if (enabled) RANDOMIZATION_PERSISTENT else RANDOMIZATION_NONE @@ -393,7 +402,7 @@ data class SoftApConfigurationCompat( @Deprecated("Class deprecated in framework, use toPlatform().toWifiConfiguration()") @Suppress("DEPRECATION") fun toWifiConfiguration(): android.net.wifi.WifiConfiguration { - val (band, channel) = requireSingleBand() + val (band, channel) = requireSingleBand(channels) 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() @@ -437,11 +446,8 @@ data class SoftApConfigurationCompat( val builder = if (sac == null) classBuilder.newInstance() else newBuilder.newInstance(sac) setSsid(builder, ssid) setPassphrase(builder, if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) null else passphrase, - securityType) - if (Build.VERSION.SDK_INT >= 31) setChannels(builder, channels) else { - val (band, channel) = requireSingleBand() - if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band) - } + securityType) + setChannelsCompat(builder, channels) setBssid(builder, bssid?.toPlatform()) setMaxNumberOfClients(builder, maxNumberOfClients) setShutdownTimeoutMillis(builder, shutdownTimeoutMillis) 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 de86fd88..fcb13a4c 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 @@ -95,6 +95,19 @@ class WifiApDialogFragment : AlertDialogFragment= 0) append(band, channel) + } + }.let { + if (arg.p2pMode || Build.VERSION.SDK_INT < 31 || !dialogView.bridgedMode.isChecked || it.size() > 2) { + SoftApConfigurationCompat.optimizeChannels(it) + } else 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 { @@ -107,19 +120,7 @@ class WifiApDialogFragment : AlertDialogFragment if (text.isNullOrEmpty()) 0 else text.toString().toLong() } - if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) { - 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.append(band, channel) - } - if (!arg.p2pMode && Build.VERSION.SDK_INT >= 31 && dialogView.bridgedMode.isChecked) { - this.channels = channels - } else optimizeChannels(channels) - } + if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) channels = generateChannels() bssid = if (dialogView.bssid.length() != 0) { MacAddressCompat.fromString(dialogView.bssid.text.toString()) } else null @@ -291,29 +292,28 @@ 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 - } + val bandError = if (arg.p2pMode || Build.VERSION.SDK_INT <= 30) { + val option5G = dialogView.band5G.selectedItem + val valid = 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 + if (valid) null else "" + } else { + if (Build.VERSION.SDK_INT >= 31) setBridgedMode() + try { + SoftApConfigurationCompat.testPlatformValidity(generateChannels()) + null + } catch (e: Exception) { + e.readableMessage } - else -> setBridgedMode() } + dialogView.bandError.isGone = bandError.isNullOrEmpty() + dialogView.bandError.text = bandError dialogView.bssidWrapper.error = null val bssidValid = dialogView.bssid.length() == 0 || try { MacAddressCompat.fromString(dialogView.bssid.text.toString()) @@ -353,7 +353,7 @@ class WifiApDialogFragment : AlertDialogFragment - + + Date: Sun, 5 Sep 2021 12:47:47 +0800 Subject: [PATCH 096/285] Use main thread for callback and add registerNetworkCallbackCompat --- .../vpnhotspot/net/monitor/DefaultNetworkMonitor.kt | 9 +++++---- .../be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt | 2 +- .../java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt | 2 +- .../AutoCompleteNetworkPreferenceDialogFragment.kt | 2 +- .../src/main/java/be/mygod/vpnhotspot/util/Services.kt | 9 +++++++++ 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt index 6cbe6661..62166999 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/DefaultNetworkMonitor.kt @@ -6,8 +6,6 @@ import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import android.os.Build -import android.os.Handler -import android.os.Looper import be.mygod.vpnhotspot.util.Services import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder import kotlinx.coroutines.GlobalScope @@ -57,9 +55,12 @@ object DefaultNetworkMonitor : UpstreamMonitor() { when (Build.VERSION.SDK_INT) { in 31..Int.MAX_VALUE -> @TargetApi(31) { Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback, - Handler(Looper.getMainLooper())) + Services.mainHandler) } - in 24..27 -> @TargetApi(24) { + in 26..27 -> @TargetApi(26) { + Services.connectivity.registerDefaultNetworkCallback(networkCallback, Services.mainHandler) + } + in 24..25 -> @TargetApi(24) { Services.connectivity.registerDefaultNetworkCallback(networkCallback) } else -> try { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt index e9c9913a..81be3b7a 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/InterfaceMonitor.kt @@ -77,7 +77,7 @@ class InterfaceMonitor(private val ifaceRegex: String) : UpstreamMonitor() { callback.onAvailable(currentLinkProperties) } } else { - Services.connectivity.registerNetworkCallback(request, networkCallback) + Services.registerNetworkCallbackCompat(request, networkCallback) registered = true } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt index 880ba10b..5aced6c9 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt @@ -61,7 +61,7 @@ object VpnMonitor : UpstreamMonitor() { callback.onAvailable(currentLinkProperties) } } else { - Services.connectivity.registerNetworkCallback(request, networkCallback) + Services.registerNetworkCallbackCompat(request, networkCallback) registered = true } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt index c1ba8e30..168b8f75 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/preference/AutoCompleteNetworkPreferenceDialogFragment.kt @@ -63,7 +63,7 @@ class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFrag override fun onStart() { super.onStart() - Services.connectivity.registerNetworkCallback(globalNetworkRequestBuilder().apply { + Services.registerNetworkCallbackCompat(globalNetworkRequestBuilder().apply { removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt index 4d17d675..acb6b108 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/Services.kt @@ -2,8 +2,12 @@ package be.mygod.vpnhotspot.util import android.content.Context import android.net.ConnectivityManager +import android.net.NetworkRequest import android.net.wifi.WifiManager import android.net.wifi.p2p.WifiP2pManager +import android.os.Build +import android.os.Handler +import android.os.Looper import androidx.core.content.getSystemService import timber.log.Timber @@ -14,6 +18,7 @@ object Services { contextInit = context } + val mainHandler by lazy { Handler(Looper.getMainLooper()) } val connectivity by lazy { context.getSystemService()!! } val p2p by lazy { try { @@ -24,4 +29,8 @@ object Services { } } val wifi by lazy { context.getSystemService()!! } + + fun registerNetworkCallbackCompat(request: NetworkRequest, networkCallback: ConnectivityManager.NetworkCallback) = + if (Build.VERSION.SDK_INT >= 26) connectivity.registerNetworkCallback(request, networkCallback, mainHandler) + else connectivity.registerNetworkCallback(request, networkCallback) } From 551dd0619b9111328badc697e44554104e00150a Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 6 Sep 2021 00:19:37 +0800 Subject: [PATCH 097/285] Update dependencies --- mobile/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle.kts b/mobile/build.gradle.kts index d2f02c65..6ecee266 100644 --- a/mobile/build.gradle.kts +++ b/mobile/build.gradle.kts @@ -83,7 +83,7 @@ dependencies { implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.android.billingclient:billing-ktx:4.0.0") implementation("com.google.android.gms:play-services-oss-licenses:17.0.0") - implementation("com.google.android.material:material:1.5.0-alpha02") + implementation("com.google.android.material:material:1.5.0-alpha03") implementation("com.google.firebase:firebase-analytics-ktx:19.0.1") implementation("com.google.firebase:firebase-crashlytics:18.2.1") implementation("com.google.zxing:core:3.4.1") @@ -91,7 +91,7 @@ dependencies { implementation("com.linkedin.dexmaker:dexmaker:2.28.1") implementation("com.takisoft.preferencex:preferencex-simplemenu:1.1.0") implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.4") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.room:room-testing:$roomVersion") androidTestImplementation("androidx.test:runner:1.4.0") From 89e6d39b6e448cb40f0d2ce5b2e61120d58afc8f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 5 Sep 2021 12:37:22 -0400 Subject: [PATCH 098/285] Material 3 --- mobile/src/main/res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml index 8f249d97..43173fa1 100644 --- a/mobile/src/main/res/values/styles.xml +++ b/mobile/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ - +