Fix repeater stuck when Location is off on Android 11+

This commit is contained in:
Mygod
2021-06-04 01:25:07 -04:00
parent 44674b7e8a
commit 880843c8cb
6 changed files with 73 additions and 25 deletions

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.res.Configuration import android.content.res.Configuration
import android.location.LocationManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.Size import androidx.annotation.Size
@@ -124,6 +125,7 @@ class App : Application() {
} }
val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) } val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) }
val clipboard by lazy { getSystemService<ClipboardManager>()!! } val clipboard by lazy { getSystemService<ClipboardManager>()!! }
val location by lazy { getSystemService<LocationManager>() }
val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") } val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") }
val customTabsIntent by lazy { val customTabsIntent by lazy {

View File

@@ -6,6 +6,7 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.LocationManager
import android.net.wifi.WpsInfo import android.net.wifi.WpsInfo
import android.net.wifi.p2p.* import android.net.wifi.p2p.*
import android.os.Build 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.SoftApConfigurationCompat
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup 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.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.requestPersistentGroupInfo
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.setWifiP2pChannels
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.startWps
@@ -212,6 +216,9 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onP2pConnectionChanged( WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onP2pConnectionChanged(
intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO), intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO),
intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)) 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 -> 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 * 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 // bump self to foreground location service (API 29+) to use location later, also to avoid getting killed
if (Build.VERSION.SDK_INT >= 26) showNotification() if (Build.VERSION.SDK_INT >= 26) showNotification()
launch { launch {
registerReceiver(receiver, intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, val filter = intentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
WifiP2pManager.WIFI_P2P_CONNECTION_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 receiverRegistered = true
try { val group = p2pManager.requestGroupInfo(channel)
p2pManager.requestGroupInfo(channel) { when {
when { group == null -> doStart()
it == null -> doStart() group.isGroupOwner -> if (routingManager == null) doStartLocked(group)
it.isGroupOwner -> launch { if (routingManager == null) doStartLocked(it) } else -> {
else -> { Timber.i("Removing old group ($group)")
Timber.i("Removing old group ($it)") p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() {
override fun onSuccess() { launch { doStart() }
doStart()
}
override fun onFailure(reason: Int) =
startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason))
})
} }
} 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 return START_NOT_STICKY
@@ -348,15 +375,19 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
/** /**
* startService Step 2 (if a group isn't already available) * startService Step 2 (if a group isn't already available)
*/ */
private fun doStart() = launch { private suspend fun doStart() {
val listener = object : WifiP2pManager.ActionListener { val listener = object : WifiP2pManager.ActionListener {
override fun onFailure(reason: Int) { override fun onFailure(reason: Int) {
startFailure(formatReason(R.string.repeater_create_group_failure, reason), startFailure(formatReason(R.string.repeater_create_group_failure, reason),
showWifiEnable = reason == WifiP2pManager.BUSY) 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) { if (!safeMode) {
binder.fetchPersistentGroup() binder.fetchPersistentGroup()
setOperatingChannel() setOperatingChannel()
@@ -466,6 +497,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
private fun cleanLocked() { private fun cleanLocked() {
if (receiverRegistered) { if (receiverRegistered) {
ensureReceiverUnregistered(receiver) ensureReceiverUnregistered(receiver)
p2pPoller?.cancel()
receiverRegistered = false receiverRegistered = false
} }
if (Build.VERSION.SDK_INT >= 28) { if (Build.VERSION.SDK_INT >= 28) {

View File

@@ -66,7 +66,7 @@ class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager()
if (if (Build.VERSION.SDK_INT < 28) @Suppress("DEPRECATION") { if (if (Build.VERSION.SDK_INT < 28) @Suppress("DEPRECATION") {
Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE, Settings.Secure.getInt(context.contentResolver, Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_OFF Settings.Secure.LOCATION_MODE_OFF) == Settings.Secure.LOCATION_MODE_OFF
} else context.getSystemService<LocationManager>()?.isLocationEnabled != true) try { } else app.location?.isLocationEnabled != true) try {
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show() Toast.makeText(context, R.string.tethering_temp_hotspot_location, Toast.LENGTH_LONG).show()
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {

View File

@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.net.wifi
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.wifi.WpsInfo import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pGroup import android.net.wifi.p2p.WifiP2pGroup
import android.net.wifi.p2p.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager import android.net.wifi.p2p.WifiP2pManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
@@ -128,7 +129,9 @@ object WifiP2pManagerHelper {
return result.await() return result.await()
} }
@SuppressLint("MissingPermission") suspend fun WifiP2pManager.requestConnectionInfo(c: WifiP2pManager.Channel) =
CompletableDeferred<WifiP2pInfo?>().apply { requestConnectionInfo(c) { complete(it) } }.await()
@SuppressLint("MissingPermission") // missing permission simply leads to null result
@RequiresApi(29) @RequiresApi(29)
suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? { suspend fun WifiP2pManager.requestDeviceAddress(c: WifiP2pManager.Channel): MacAddressCompat? {
val future = CompletableDeferred<String?>() val future = CompletableDeferred<String?>()
@@ -138,4 +141,10 @@ object WifiP2pManagerHelper {
if (address == MacAddressCompat.ANY_ADDRESS) null else address 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<WifiP2pGroup?>().apply { requestGroupInfo(c) { complete(it) } }.await()
@RequiresApi(29)
suspend fun WifiP2pManager.requestP2pState(c: WifiP2pManager.Channel) =
CompletableDeferred<Int>().apply { requestP2pState(c) { complete(it) } }.await()
} }

View File

@@ -29,6 +29,8 @@
<string name="repeater_failure_reason_unsupported_operation">不支持此操作</string> <string name="repeater_failure_reason_unsupported_operation">不支持此操作</string>
<string name="repeater_failure_disconnected">服务不可用,请稍后重试</string> <string name="repeater_failure_disconnected">服务不可用,请稍后重试</string>
<string name="repeater_missing_location_permissions">无线中继需要精确位置权限</string> <string name="repeater_missing_location_permissions">无线中继需要精确位置权限</string>
<string name="repeater_location_off">由于系统限制,关闭位置信息服务可能产生问题并导致续航缩短</string>
<string name="repeater_location_off_configure">进入设置</string>
<string name="tethering_temp_hotspot">临时 WLAN 热点</string> <string name="tethering_temp_hotspot">临时 WLAN 热点</string>
<string name="tethering_temp_hotspot_location">使用临时热点需要打开位置服务。</string> <string name="tethering_temp_hotspot_location">使用临时热点需要打开位置服务。</string>

View File

@@ -47,6 +47,9 @@
<string name="repeater_failure_disconnected">Service unavailable. Try again later</string> <string name="repeater_failure_disconnected">Service unavailable. Try again later</string>
<string name="repeater_missing_location_permissions">Repeater requires permissions for accessing fine <string name="repeater_missing_location_permissions">Repeater requires permissions for accessing fine
location</string> location</string>
<string name="repeater_location_off">Due to system restrictions, turning Location off may lead to things not working
properly and increased battery usage</string>
<string name="repeater_location_off_configure">Configure</string>
<string name="tethering_temp_hotspot">Temporary Wi\u2011Fi hotspot</string> <string name="tethering_temp_hotspot">Temporary Wi\u2011Fi hotspot</string>
<string name="tethering_temp_hotspot_location">Temporary hotspot requires location to be turned on.</string> <string name="tethering_temp_hotspot_location">Temporary hotspot requires location to be turned on.</string>