Fix repeater stuck when Location is off on Android 11+
This commit is contained in:
@@ -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
|
||||||
@@ -123,6 +124,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 {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -215,6 +219,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 ->
|
||||||
@@ -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
|
* startService Step 1
|
||||||
*/
|
*/
|
||||||
@@ -321,19 +352,20 @@ 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 {
|
||||||
it == null -> doStart()
|
group == null -> doStart()
|
||||||
it.isGroupOwner -> launch { if (routingManager == null) doStartLocked(it) }
|
group.isGroupOwner -> if (routingManager == null) doStartLocked(group)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.i("Removing old group ($it)")
|
Timber.i("Removing old group ($group)")
|
||||||
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
p2pManager.removeGroup(channel, object : WifiP2pManager.ActionListener {
|
||||||
override fun onSuccess() {
|
override fun onSuccess() {
|
||||||
doStart()
|
launch { doStart() }
|
||||||
}
|
}
|
||||||
override fun onFailure(reason: Int) =
|
override fun onFailure(reason: Int) =
|
||||||
startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason))
|
startFailure(formatReason(R.string.repeater_remove_old_group_failure, reason))
|
||||||
@@ -341,25 +373,24 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: SecurityException) {
|
|
||||||
Timber.w(e)
|
|
||||||
startFailure(e.readableMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 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()
|
||||||
@@ -487,6 +518,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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -129,7 +130,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?>()
|
||||||
@@ -139,4 +142,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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user