diff --git a/mobile/build.gradle b/mobile/build.gradle
index db13b973..e5577006 100644
--- a/mobile/build.gradle
+++ b/mobile/build.gradle
@@ -74,6 +74,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.linkedin.dexmaker:dexmaker-mockito:2.19.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
+ implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
baseImplementation 'com.android.billingclient:billing:1.2'
baseImplementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 31feceb2..772364d5 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -33,6 +33,7 @@
tools:ignore="ProtectedPermissions"/>
+
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt
index 166cc96c..41b503ab 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt
@@ -8,6 +8,7 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.manage.LocalOnlyHotspotManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.TetheringManager
+import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber
@@ -30,6 +31,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private val binder = Binder()
private var reservation: WifiManager.LocalOnlyHotspotReservation? = null
private var routingManager: LocalOnlyInterfaceManager? = null
+ private var locked = false
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
val ifaces = TetheringManager.getLocalOnlyTetheredIfaces(intent.extras ?: return@broadcastReceiver)
@@ -63,6 +65,9 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {
if (reservation == null) onFailed(-2) else {
this@LocalOnlyHotspotService.reservation = reservation
+ check(!locked)
+ WifiDoubleLock.acquire()
+ locked = true
if (!receiverRegistered) {
registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
receiverRegistered = true
@@ -119,6 +124,10 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private fun unregisterReceiver() {
routingManager?.stop()
routingManager = null
+ if (locked) {
+ WifiDoubleLock.release()
+ locked = false
+ }
if (receiverRegistered) {
unregisterReceiver(receiver)
IpNeighbourMonitor.unregisterCallback(this)
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
index ecac92b6..781cc918 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/RepeaterService.kt
@@ -15,6 +15,7 @@ import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import androidx.core.os.postDelayed
import be.mygod.vpnhotspot.App.Companion.app
+import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.netId
@@ -123,6 +124,7 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
}
}
private var routingManager: LocalOnlyInterfaceManager? = null
+ private var locked = false
var status = Status.IDLE
private set(value) {
@@ -261,6 +263,9 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
* startService Step 3
*/
private fun doStart(group: WifiP2pGroup) {
+ check(!locked)
+ WifiDoubleLock.acquire()
+ locked = true
binder.group = group
check(routingManager == null)
routingManager = LocalOnlyInterfaceManager(group.`interface`!!)
@@ -297,6 +302,10 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
unregisterReceiver()
routingManager?.stop()
routingManager = null
+ if (locked) {
+ WifiDoubleLock.release()
+ locked = false
+ }
status = Status.IDLE
ServiceNotification.stopForeground(this)
stopSelf()
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt
index 4a6a1e55..a02ae704 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt
@@ -5,8 +5,10 @@ import android.content.IntentFilter
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.manage.TetheringFragment
import be.mygod.vpnhotspot.net.Routing
+import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
+import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber
@@ -25,6 +27,7 @@ class TetheringService : IpNeighbourMonitoringService() {
private val binder = Binder()
private val routings = HashMap()
+ private var locked = false
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver
@@ -37,6 +40,10 @@ class TetheringService : IpNeighbourMonitoringService() {
override val activeIfaces get() = synchronized(routings) { routings.keys.toList() }
private fun updateRoutingsLocked() {
+ if (locked && routings.keys.all { !TetherType.ofInterface(it).isWifi }) {
+ WifiDoubleLock.release()
+ locked = false
+ }
if (routings.isEmpty()) {
unregisterReceiver()
ServiceNotification.stopForeground(this)
@@ -92,7 +99,13 @@ class TetheringService : IpNeighbourMonitoringService() {
if (intent != null) {
val iface = intent.getStringExtra(EXTRA_ADD_INTERFACE)
synchronized(routings) {
- if (iface != null) routings[iface] = null
+ if (iface != null) {
+ routings[iface] = null
+ if (TetherType.ofInterface(iface).isWifi && !locked) {
+ WifiDoubleLock.acquire()
+ locked = true
+ }
+ }
routings.remove(intent.getStringExtra(EXTRA_REMOVE_INTERFACE))?.revert()
updateRoutingsLocked()
}
diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt
index b6101430..99ba30ad 100644
--- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt
@@ -15,6 +15,10 @@ enum class TetherType {
BLUETOOTH -> R.drawable.ic_device_bluetooth
else -> R.drawable.ic_device_wifi_tethering
}
+ val isWifi get() = when (this) {
+ WIFI_P2P, WIFI, WIMAX -> true
+ else -> false
+ }
companion object {
private val usbRegexes: List
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
new file mode 100644
index 00000000..b1831503
--- /dev/null
+++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiDoubleLock.kt
@@ -0,0 +1,61 @@
+package be.mygod.vpnhotspot.net.wifi
+
+import android.annotation.SuppressLint
+import android.content.SharedPreferences
+import android.net.wifi.WifiManager
+import android.os.PowerManager
+import androidx.core.content.getSystemService
+import be.mygod.vpnhotspot.App.Companion.app
+
+/**
+ * This mechanism is used to maximize profit. Source: https://stackoverflow.com/a/29657230/2245107
+ */
+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
+ private val service by lazy { app.getSystemService()!! }
+
+ private var referenceCount = 0
+ private var lock: WifiDoubleLock? = null
+
+ fun acquire() = synchronized(this) {
+ if (referenceCount == 0) {
+ app.pref.registerOnSharedPreferenceChangeListener(this)
+ val lockType = lockType
+ if (lockType != null) lock = WifiDoubleLock(lockType)
+ }
+ referenceCount += 1
+ }
+ fun release() = synchronized(this) {
+ referenceCount -= 1
+ if (referenceCount == 0) {
+ lock?.close()
+ lock = null
+ app.pref.unregisterOnSharedPreferenceChangeListener(this)
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ if (key == KEY) synchronized(this) {
+ lock?.close()
+ val lockType = 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)
+ }
+
+ private val wifi = app.wifi.createWifiLock(lockType, "vpnhotspot:wifi").apply { acquire() }
+ @SuppressLint("WakelockTimeout")
+ private val power = service.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "vpnhotspot:power").apply { acquire() }
+
+ override fun close() {
+ wifi.release()
+ power.release()
+ }
+}
diff --git a/mobile/src/main/res/drawable/ic_device_wifi_lock.xml b/mobile/src/main/res/drawable/ic_device_wifi_lock.xml
index 8eba49d0..877f0d44 100644
--- a/mobile/src/main/res/drawable/ic_device_wifi_lock.xml
+++ b/mobile/src/main/res/drawable/ic_device_wifi_lock.xml
@@ -2,7 +2,9 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml
index b5f96d9d..e04641bb 100644
--- a/mobile/src/main/res/values-zh-rCN/strings.xml
+++ b/mobile/src/main/res/values-zh-rCN/strings.xml
@@ -84,6 +84,10 @@
禁用 IPv6 共享
防止 IPv6 VPN 泄漏。
开机自启动中继
+ 保持 Wi\u2011Fi 开启
+ 系统默认
+ 开
+ 高性能模式
备用 DNS 服务器[:端口]
上游网络接口
自动检测系统 VPN
diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml
new file mode 100644
index 00000000..65dbb003
--- /dev/null
+++ b/mobile/src/main/res/values/arrays.xml
@@ -0,0 +1,13 @@
+
+
+
+ - @string/settings_service_wifi_lock_none
+ - @string/settings_service_wifi_lock_full
+ - @string/settings_service_wifi_lock_high_perf
+
+
+ - None
+ - Full
+ - HighPerf
+
+
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index f6a5a3e6..189fe0c4 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -92,6 +92,10 @@
Disable IPv6 tethering
Enabling this option will prevent VPN leaks via IPv6.
Start repeater on boot
+ Keep Wi\u2011Fi alive
+ System default
+ On
+ High Performance Mode
Fallback DNS server[:port]
Upstream network interface
Auto detect system VPN
diff --git a/mobile/src/main/res/xml/pref_settings.xml b/mobile/src/main/res/xml/pref_settings.xml
index 9f689c49..5caaf5c0 100644
--- a/mobile/src/main/res/xml/pref_settings.xml
+++ b/mobile/src/main/res/xml/pref_settings.xml
@@ -35,6 +35,14 @@
android:icon="@drawable/ic_image_looks_6"
android:title="@string/settings_service_disable_ipv6"
android:summary="@string/settings_service_disable_ipv6_summary"/>
+