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.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<ClipboardManager>()!! }
val location by lazy { getSystemService<LocationManager>() }
val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") }
val customTabsIntent by lazy {

View File

@@ -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) {

View File

@@ -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<LocationManager>()?.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) {

View File

@@ -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<WifiP2pInfo?>().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<String?>()
@@ -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<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_disconnected">服务不可用,请稍后重试</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_location">使用临时热点需要打开位置服务。</string>

View File

@@ -47,6 +47,9 @@
<string name="repeater_failure_disconnected">Service unavailable. Try again later</string>
<string name="repeater_missing_location_permissions">Repeater requires permissions for accessing fine
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_location">Temporary hotspot requires location to be turned on.</string>