Support new Wi-Fi locks in Android Q
This commit is contained in:
16
README.md
16
README.md
@@ -65,8 +65,20 @@ Default settings are picked to suit general use cases and maximize compatibility
|
|||||||
|
|
||||||
* Keep Wi-Fi alive: Acquire Wi-Fi locks when repeater, temporary hotspot or system VPN hotspot is activated.
|
* Keep Wi-Fi alive: Acquire Wi-Fi locks when repeater, temporary hotspot or system VPN hotspot is activated.
|
||||||
- Choose "System default" to save battery life;
|
- Choose "System default" to save battery life;
|
||||||
- Choose "On" (default) if repeater/hotspot turns itself off automatically or stops working after a while;
|
- (up to Android 9) Choose "On" (default) if repeater/hotspot turns itself off automatically or stops working after a while;
|
||||||
- Choose "High Performance Mode" to minimize packet loss and latency (will consume more power).
|
- (up to Android 9) Choose "High Performance Mode" to minimize packet loss and latency (will consume more power);
|
||||||
|
- (since Android Q) Choose "Disable power save" to decrease packet latency.
|
||||||
|
An example use case is when a voice connection needs to be kept active even after the device screen goes off.
|
||||||
|
Using this mode may improve the call quality.
|
||||||
|
Requires support from the hardware.
|
||||||
|
- (since Android Q) Choose "Low latency mode" to optimize for reduced packet latency, and this might result in:
|
||||||
|
1. Reduced battery life.
|
||||||
|
2. Reduced throughput.
|
||||||
|
3. Reduced frequency of Wi-Fi scanning.
|
||||||
|
This may cause the device not roaming or switching to the AP with highest signal quality, and location accuracy may be reduced.
|
||||||
|
Example use cases are real time gaming or virtual reality applications where low latency is a key factor for user experience.
|
||||||
|
Requires support from the hardware.
|
||||||
|
Note: Requires this app running in foreground with screen on.
|
||||||
* Start repeater on boot: Self explanatory.
|
* Start repeater on boot: Self explanatory.
|
||||||
* Network status monitor mode: This option controls how the app monitors connected devices as well as interface changes
|
* Network status monitor mode: This option controls how the app monitors connected devices as well as interface changes
|
||||||
(when custom upstream is used).
|
(when custom upstream is used).
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import be.mygod.vpnhotspot.client.ClientViewModel
|
|||||||
import be.mygod.vpnhotspot.client.ClientsFragment
|
import be.mygod.vpnhotspot.client.ClientsFragment
|
||||||
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
|
import be.mygod.vpnhotspot.databinding.ActivityMainBinding
|
||||||
import be.mygod.vpnhotspot.manage.TetheringFragment
|
import be.mygod.vpnhotspot.manage.TetheringFragment
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
||||||
@@ -41,6 +42,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
|||||||
if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class)
|
if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class)
|
||||||
model.clients.observe(this, Observer { badge.badgeNumber = it.size })
|
model.clients.observe(this, Observer { badge.badgeNumber = it.size })
|
||||||
SmartSnackbar.Register(lifecycle, binding.fragmentHolder)
|
SmartSnackbar.Register(lifecycle, binding.fragmentHolder)
|
||||||
|
WifiDoubleLock.ActivityListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNavigationItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onNavigationItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpMonitor
|
||||||
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
|
||||||
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
|
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragmentCompat
|
||||||
import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore
|
import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore
|
||||||
import be.mygod.vpnhotspot.util.RootSession
|
import be.mygod.vpnhotspot.util.RootSession
|
||||||
@@ -26,6 +27,7 @@ import java.net.SocketException
|
|||||||
|
|
||||||
class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
WifiDoubleLock.mode = WifiDoubleLock.mode // handle complicated default value and possible system upgrades
|
||||||
preferenceManager.preferenceDataStore = SharedPreferenceDataStore(app.pref)
|
preferenceManager.preferenceDataStore = SharedPreferenceDataStore(app.pref)
|
||||||
RoutingManager.masqueradeMode = RoutingManager.masqueradeMode // flush default value
|
RoutingManager.masqueradeMode = RoutingManager.masqueradeMode // flush default value
|
||||||
addPreferencesFromResource(R.xml.pref_settings)
|
addPreferencesFromResource(R.xml.pref_settings)
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
|
import androidx.core.os.BuildCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,8 +21,12 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
class WifiDoubleLock(lockType: Int) : AutoCloseable {
|
class WifiDoubleLock(lockType: Int) : AutoCloseable {
|
||||||
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
companion object : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private const val KEY = "service.wifiLock"
|
private const val KEY = "service.wifiLock"
|
||||||
private val lockType get() =
|
var mode: Mode
|
||||||
WifiDoubleLock.Mode.valueOf(app.pref.getString(KEY, WifiDoubleLock.Mode.Full.toString()) ?: "").lockType
|
@Suppress("DEPRECATION")
|
||||||
|
get() = Mode.valueOf(app.pref.getString(KEY, Mode.Full.toString()) ?: "").let {
|
||||||
|
if (it == Mode.Full && BuildCompat.isAtLeastQ()) Mode.None else it
|
||||||
|
}
|
||||||
|
set(value) = app.pref.edit { putString(KEY, value.toString()) }
|
||||||
private val service by lazy { app.getSystemService<PowerManager>()!! }
|
private val service by lazy { app.getSystemService<PowerManager>()!! }
|
||||||
|
|
||||||
private var holders = mutableSetOf<Any>()
|
private var holders = mutableSetOf<Any>()
|
||||||
@@ -23,7 +35,7 @@ class WifiDoubleLock(lockType: Int) : AutoCloseable {
|
|||||||
fun acquire(holder: Any) = synchronized(this) {
|
fun acquire(holder: Any) = synchronized(this) {
|
||||||
if (holders.isEmpty()) {
|
if (holders.isEmpty()) {
|
||||||
app.pref.registerOnSharedPreferenceChangeListener(this)
|
app.pref.registerOnSharedPreferenceChangeListener(this)
|
||||||
val lockType = lockType
|
val lockType = mode.lockType
|
||||||
if (lockType != null) lock = WifiDoubleLock(lockType)
|
if (lockType != null) lock = WifiDoubleLock(lockType)
|
||||||
}
|
}
|
||||||
check(holders.add(holder))
|
check(holders.add(holder))
|
||||||
@@ -40,14 +52,45 @@ class WifiDoubleLock(lockType: Int) : AutoCloseable {
|
|||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
if (key == KEY) synchronized(this) {
|
if (key == KEY) synchronized(this) {
|
||||||
lock?.close()
|
lock?.close()
|
||||||
val lockType = lockType
|
val lockType = mode.lockType
|
||||||
lock = if (lockType == null) null else WifiDoubleLock(lockType)
|
lock = if (lockType == null) null else WifiDoubleLock(lockType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode(val lockType: Int? = null) {
|
enum class Mode(val lockType: Int? = null, val keepScreenOn: Boolean = false) {
|
||||||
None, Full(WifiManager.WIFI_MODE_FULL), HighPerf(WifiManager.WIFI_MODE_FULL_HIGH_PERF)
|
None,
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@Deprecated("This constant was deprecated in API level Q.\n" +
|
||||||
|
"This API is non-functional and will have no impact.")
|
||||||
|
Full(WifiManager.WIFI_MODE_FULL),
|
||||||
|
HighPerf(WifiManager.WIFI_MODE_FULL_HIGH_PERF),
|
||||||
|
@RequiresApi(29)
|
||||||
|
LowLatency(WifiManager.WIFI_MODE_FULL_LOW_LATENCY, true),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActivityListener(val activity: ComponentActivity) :
|
||||||
|
LifecycleObserver, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
private var keepScreenOn: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (field == value) return
|
||||||
|
field = value
|
||||||
|
if (value) activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
else activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
activity.lifecycle.addObserver(this)
|
||||||
|
app.pref.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
keepScreenOn = mode.keepScreenOn
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
if (key == KEY) keepScreenOn = mode.keepScreenOn
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||||
|
fun shutdown() = app.pref.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val wifi = app.wifi.createWifiLock(lockType, "vpnhotspot:wifi").apply { acquire() }
|
private val wifi = app.wifi.createWifiLock(lockType, "vpnhotspot:wifi").apply { acquire() }
|
||||||
|
|||||||
13
mobile/src/main/res/values-v29/arrays.xml
Normal file
13
mobile/src/main/res/values-v29/arrays.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string-array name="settings_service_wifi_lock">
|
||||||
|
<item>@string/settings_service_wifi_lock_none</item>
|
||||||
|
<item>@string/settings_service_wifi_lock_high_perf_v29</item>
|
||||||
|
<item>@string/settings_service_wifi_lock_low_latency</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_service_wifi_lock_values">
|
||||||
|
<item>None</item>
|
||||||
|
<item>HighPerf</item>
|
||||||
|
<item>LowLatency</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
@@ -101,6 +101,8 @@
|
|||||||
<string name="settings_service_wifi_lock_none">System default</string>
|
<string name="settings_service_wifi_lock_none">System default</string>
|
||||||
<string name="settings_service_wifi_lock_full">On</string>
|
<string name="settings_service_wifi_lock_full">On</string>
|
||||||
<string name="settings_service_wifi_lock_high_perf">High Performance Mode</string>
|
<string name="settings_service_wifi_lock_high_perf">High Performance Mode</string>
|
||||||
|
<string name="settings_service_wifi_lock_high_perf_v29">Disable power save</string>
|
||||||
|
<string name="settings_service_wifi_lock_low_latency">Low latency mode</string>
|
||||||
<string name="settings_service_ip_monitor">Network status monitor mode</string>
|
<string name="settings_service_ip_monitor">Network status monitor mode</string>
|
||||||
<string name="settings_service_ip_monitor_monitor">Netlink monitor</string>
|
<string name="settings_service_ip_monitor_monitor">Netlink monitor</string>
|
||||||
<string name="settings_service_ip_monitor_monitor_root">Netlink monitor with root</string>
|
<string name="settings_service_ip_monitor_monitor_root">Netlink monitor with root</string>
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
app:icon="@drawable/ic_device_wifi_lock"
|
app:icon="@drawable/ic_device_wifi_lock"
|
||||||
app:entries="@array/settings_service_wifi_lock"
|
app:entries="@array/settings_service_wifi_lock"
|
||||||
app:entryValues="@array/settings_service_wifi_lock_values"
|
app:entryValues="@array/settings_service_wifi_lock_values"
|
||||||
app:defaultValue="Full"
|
|
||||||
app:title="@string/settings_service_wifi_lock"
|
app:title="@string/settings_service_wifi_lock"
|
||||||
app:useSimpleSummaryProvider="true"/>
|
app:useSimpleSummaryProvider="true"/>
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
|
|||||||
Reference in New Issue
Block a user