From 880843c8cbeb41c5ca4d3d66faf4d9fd4b52686b Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 4 Jun 2021 01:25:07 -0400 Subject: [PATCH] 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.