diff --git a/README.md b/README.md index c4e45603..7aadba8f 100644 --- a/README.md +++ b/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. - Choose "System default" to save battery life; - - 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 "On" (default) if repeater/hotspot turns itself off automatically or stops working after a while; + - (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. * Network status monitor mode: This option controls how the app monitors connected devices as well as interface changes (when custom upstream is used). diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt index 4b859996..4914c620 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/MainActivity.kt @@ -15,6 +15,7 @@ import be.mygod.vpnhotspot.client.ClientViewModel import be.mygod.vpnhotspot.client.ClientsFragment import be.mygod.vpnhotspot.databinding.ActivityMainBinding import be.mygod.vpnhotspot.manage.TetheringFragment +import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.widget.SmartSnackbar import com.google.android.material.bottomnavigation.BottomNavigationMenuView @@ -41,6 +42,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS if (RepeaterService.supported) ServiceForegroundConnector(this, model, RepeaterService::class) model.clients.observe(this, Observer { badge.badgeNumber = it.size }) SmartSnackbar.Register(lifecycle, binding.fragmentHolder) + WifiDoubleLock.ActivityListener(this) } override fun onNavigationItemSelected(item: MenuItem) = when (item.itemId) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt index 5a4faf26..e1b5986f 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/SettingsPreferenceFragment.kt @@ -12,6 +12,7 @@ import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES import be.mygod.vpnhotspot.net.monitor.IpMonitor 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.SharedPreferenceDataStore import be.mygod.vpnhotspot.util.RootSession @@ -26,6 +27,7 @@ import java.net.SocketException class SettingsPreferenceFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + WifiDoubleLock.mode = WifiDoubleLock.mode // handle complicated default value and possible system upgrades preferenceManager.preferenceDataStore = SharedPreferenceDataStore(app.pref) RoutingManager.masqueradeMode = RoutingManager.masqueradeMode // flush default value addPreferencesFromResource(R.xml.pref_settings) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiDoubleLock.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiDoubleLock.kt index 45261e1b..7032d710 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiDoubleLock.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiDoubleLock.kt @@ -4,7 +4,15 @@ import android.annotation.SuppressLint import android.content.SharedPreferences import android.net.wifi.WifiManager 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.os.BuildCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent import be.mygod.vpnhotspot.App.Companion.app /** @@ -13,8 +21,12 @@ import be.mygod.vpnhotspot.App.Companion.app class WifiDoubleLock(lockType: Int) : AutoCloseable { companion object : SharedPreferences.OnSharedPreferenceChangeListener { private const val KEY = "service.wifiLock" - private val lockType get() = - WifiDoubleLock.Mode.valueOf(app.pref.getString(KEY, WifiDoubleLock.Mode.Full.toString()) ?: "").lockType + var mode: Mode + @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()!! } private var holders = mutableSetOf() @@ -23,7 +35,7 @@ class WifiDoubleLock(lockType: Int) : AutoCloseable { fun acquire(holder: Any) = synchronized(this) { if (holders.isEmpty()) { app.pref.registerOnSharedPreferenceChangeListener(this) - val lockType = lockType + val lockType = mode.lockType if (lockType != null) lock = WifiDoubleLock(lockType) } check(holders.add(holder)) @@ -40,14 +52,45 @@ class WifiDoubleLock(lockType: Int) : AutoCloseable { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { if (key == KEY) synchronized(this) { lock?.close() - val lockType = lockType + val lockType = mode.lockType lock = if (lockType == null) null else WifiDoubleLock(lockType) } } } - enum class Mode(val lockType: Int? = null) { - None, Full(WifiManager.WIFI_MODE_FULL), HighPerf(WifiManager.WIFI_MODE_FULL_HIGH_PERF) + enum class Mode(val lockType: Int? = null, val keepScreenOn: Boolean = false) { + 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() } diff --git a/mobile/src/main/res/values-v29/arrays.xml b/mobile/src/main/res/values-v29/arrays.xml new file mode 100644 index 00000000..30824b47 --- /dev/null +++ b/mobile/src/main/res/values-v29/arrays.xml @@ -0,0 +1,13 @@ + + + + @string/settings_service_wifi_lock_none + @string/settings_service_wifi_lock_high_perf_v29 + @string/settings_service_wifi_lock_low_latency + + + None + HighPerf + LowLatency + + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 70098cbb..6b8a73b8 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -101,6 +101,8 @@ System default On High Performance Mode + Disable power save + Low latency mode Network status monitor mode Netlink monitor Netlink monitor with root diff --git a/mobile/src/main/res/xml/pref_settings.xml b/mobile/src/main/res/xml/pref_settings.xml index d78bea81..1edeb6b2 100644 --- a/mobile/src/main/res/xml/pref_settings.xml +++ b/mobile/src/main/res/xml/pref_settings.xml @@ -41,7 +41,6 @@ app:icon="@drawable/ic_device_wifi_lock" app:entries="@array/settings_service_wifi_lock" app:entryValues="@array/settings_service_wifi_lock_values" - app:defaultValue="Full" app:title="@string/settings_service_wifi_lock" app:useSimpleSummaryProvider="true"/>