Support Wi-Fi keep alives

This commit is contained in:
Mygod
2018-12-23 23:35:55 +08:00
parent c4260638f2
commit 55443902be
12 changed files with 131 additions and 2 deletions

View File

@@ -74,6 +74,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.linkedin.dexmaker:dexmaker-mockito:2.19.1' implementation 'com.linkedin.dexmaker:dexmaker-mockito:2.19.1'
implementation 'com.takisoft.preferencex:preferencex:1.0.0' 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" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
baseImplementation 'com.android.billingclient:billing:1.2' baseImplementation 'com.android.billingclient:billing:1.2'
baseImplementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' baseImplementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'

View File

@@ -33,6 +33,7 @@
tools:ignore="ProtectedPermissions"/> tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.TETHER_PRIVILEGE"/> <uses-permission android:name="android.permission.TETHER_PRIVILEGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" <uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions"/> tools:ignore="ProtectedPermissions"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

View File

@@ -8,6 +8,7 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.manage.LocalOnlyHotspotManager import be.mygod.vpnhotspot.manage.LocalOnlyHotspotManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
@@ -30,6 +31,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private val binder = Binder() private val binder = Binder()
private var reservation: WifiManager.LocalOnlyHotspotReservation? = null private var reservation: WifiManager.LocalOnlyHotspotReservation? = null
private var routingManager: LocalOnlyInterfaceManager? = null private var routingManager: LocalOnlyInterfaceManager? = null
private var locked = false
private var receiverRegistered = false private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val ifaces = TetheringManager.getLocalOnlyTetheredIfaces(intent.extras ?: return@broadcastReceiver) val ifaces = TetheringManager.getLocalOnlyTetheredIfaces(intent.extras ?: return@broadcastReceiver)
@@ -63,6 +65,9 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) { override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {
if (reservation == null) onFailed(-2) else { if (reservation == null) onFailed(-2) else {
this@LocalOnlyHotspotService.reservation = reservation this@LocalOnlyHotspotService.reservation = reservation
check(!locked)
WifiDoubleLock.acquire()
locked = true
if (!receiverRegistered) { if (!receiverRegistered) {
registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
receiverRegistered = true receiverRegistered = true
@@ -119,6 +124,10 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private fun unregisterReceiver() { private fun unregisterReceiver() {
routingManager?.stop() routingManager?.stop()
routingManager = null routingManager = null
if (locked) {
WifiDoubleLock.release()
locked = false
}
if (receiverRegistered) { if (receiverRegistered) {
unregisterReceiver(receiver) unregisterReceiver(receiver)
IpNeighbourMonitor.unregisterCallback(this) IpNeighbourMonitor.unregisterCallback(this)

View File

@@ -15,6 +15,7 @@ import androidx.annotation.StringRes
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import be.mygod.vpnhotspot.App.Companion.app 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
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.deletePersistentGroup
import be.mygod.vpnhotspot.net.wifi.WifiP2pManagerHelper.netId 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 routingManager: LocalOnlyInterfaceManager? = null
private var locked = false
var status = Status.IDLE var status = Status.IDLE
private set(value) { private set(value) {
@@ -261,6 +263,9 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
* startService Step 3 * startService Step 3
*/ */
private fun doStart(group: WifiP2pGroup) { private fun doStart(group: WifiP2pGroup) {
check(!locked)
WifiDoubleLock.acquire()
locked = true
binder.group = group binder.group = group
check(routingManager == null) check(routingManager == null)
routingManager = LocalOnlyInterfaceManager(group.`interface`!!) routingManager = LocalOnlyInterfaceManager(group.`interface`!!)
@@ -297,6 +302,10 @@ class RepeaterService : Service(), WifiP2pManager.ChannelListener, SharedPrefere
unregisterReceiver() unregisterReceiver()
routingManager?.stop() routingManager?.stop()
routingManager = null routingManager = null
if (locked) {
WifiDoubleLock.release()
locked = false
}
status = Status.IDLE status = Status.IDLE
ServiceNotification.stopForeground(this) ServiceNotification.stopForeground(this)
stopSelf() stopSelf()

View File

@@ -5,8 +5,10 @@ import android.content.IntentFilter
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.manage.TetheringFragment import be.mygod.vpnhotspot.manage.TetheringFragment
import be.mygod.vpnhotspot.net.Routing import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
@@ -25,6 +27,7 @@ class TetheringService : IpNeighbourMonitoringService() {
private val binder = Binder() private val binder = Binder()
private val routings = HashMap<String, Routing?>() private val routings = HashMap<String, Routing?>()
private var locked = false
private var receiverRegistered = false private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver val extras = intent.extras ?: return@broadcastReceiver
@@ -37,6 +40,10 @@ class TetheringService : IpNeighbourMonitoringService() {
override val activeIfaces get() = synchronized(routings) { routings.keys.toList() } override val activeIfaces get() = synchronized(routings) { routings.keys.toList() }
private fun updateRoutingsLocked() { private fun updateRoutingsLocked() {
if (locked && routings.keys.all { !TetherType.ofInterface(it).isWifi }) {
WifiDoubleLock.release()
locked = false
}
if (routings.isEmpty()) { if (routings.isEmpty()) {
unregisterReceiver() unregisterReceiver()
ServiceNotification.stopForeground(this) ServiceNotification.stopForeground(this)
@@ -92,7 +99,13 @@ class TetheringService : IpNeighbourMonitoringService() {
if (intent != null) { if (intent != null) {
val iface = intent.getStringExtra(EXTRA_ADD_INTERFACE) val iface = intent.getStringExtra(EXTRA_ADD_INTERFACE)
synchronized(routings) { 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() routings.remove(intent.getStringExtra(EXTRA_REMOVE_INTERFACE))?.revert()
updateRoutingsLocked() updateRoutingsLocked()
} }

View File

@@ -15,6 +15,10 @@ enum class TetherType {
BLUETOOTH -> R.drawable.ic_device_bluetooth BLUETOOTH -> R.drawable.ic_device_bluetooth
else -> R.drawable.ic_device_wifi_tethering else -> R.drawable.ic_device_wifi_tethering
} }
val isWifi get() = when (this) {
WIFI_P2P, WIFI, WIMAX -> true
else -> false
}
companion object { companion object {
private val usbRegexes: List<Pattern> private val usbRegexes: List<Pattern>

View File

@@ -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<PowerManager>()!! }
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()
}
}

View File

@@ -2,7 +2,9 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal">
<path <path
android:fillColor="#FF000000" android:fillColor="#FF000000"
android:pathData="M20.5,9.5c0.28,0 0.55,0.04 0.81,0.08L24,6c-3.34,-2.51 -7.5,-4 -12,-4S3.34,3.49 0,6l12,16 3.5,-4.67L15.5,14.5c0,-2.76 2.24,-5 5,-5zM23,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S18,13.12 18,14.5L18,16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM22,16h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5L22,16z"/> android:pathData="M20.5,9.5c0.28,0 0.55,0.04 0.81,0.08L24,6c-3.34,-2.51 -7.5,-4 -12,-4S3.34,3.49 0,6l12,16 3.5,-4.67L15.5,14.5c0,-2.76 2.24,-5 5,-5zM23,16v-1.5c0,-1.38 -1.12,-2.5 -2.5,-2.5S18,13.12 18,14.5L18,16c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM22,16h-3v-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5L22,16z"/>

View File

@@ -84,6 +84,10 @@
<string name="settings_service_disable_ipv6">禁用 IPv6 共享</string> <string name="settings_service_disable_ipv6">禁用 IPv6 共享</string>
<string name="settings_service_disable_ipv6_summary">防止 IPv6 VPN 泄漏。</string> <string name="settings_service_disable_ipv6_summary">防止 IPv6 VPN 泄漏。</string>
<string name="settings_service_repeater_start_on_boot">开机自启动中继</string> <string name="settings_service_repeater_start_on_boot">开机自启动中继</string>
<string name="settings_service_wifi_lock">保持 Wi\u2011Fi 开启</string>
<string name="settings_service_wifi_lock_none">系统默认</string>
<string name="settings_service_wifi_lock_full"></string>
<string name="settings_service_wifi_lock_high_perf">高性能模式</string>
<string name="settings_service_dns">备用 DNS 服务器[:端口]</string> <string name="settings_service_dns">备用 DNS 服务器[:端口]</string>
<string name="settings_service_upstream">上游网络接口</string> <string name="settings_service_upstream">上游网络接口</string>
<string name="settings_service_upstream_auto">自动检测系统 VPN</string> <string name="settings_service_upstream_auto">自动检测系统 VPN</string>

View 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_full</item>
<item>@string/settings_service_wifi_lock_high_perf</item>
</string-array>
<string-array name="settings_service_wifi_lock_values">
<item>None</item>
<item>Full</item>
<item>HighPerf</item>
</string-array>
</resources>

View File

@@ -92,6 +92,10 @@
<string name="settings_service_disable_ipv6">Disable IPv6 tethering</string> <string name="settings_service_disable_ipv6">Disable IPv6 tethering</string>
<string name="settings_service_disable_ipv6_summary">Enabling this option will prevent VPN leaks via IPv6.</string> <string name="settings_service_disable_ipv6_summary">Enabling this option will prevent VPN leaks via IPv6.</string>
<string name="settings_service_repeater_start_on_boot">Start repeater on boot</string> <string name="settings_service_repeater_start_on_boot">Start repeater on boot</string>
<string name="settings_service_wifi_lock">Keep Wi\u2011Fi alive</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_high_perf">High Performance Mode</string>
<string name="settings_service_dns">Fallback DNS server[:port]</string> <string name="settings_service_dns">Fallback DNS server[:port]</string>
<string name="settings_service_upstream">Upstream network interface</string> <string name="settings_service_upstream">Upstream network interface</string>
<string name="settings_service_upstream_auto">Auto detect system VPN</string> <string name="settings_service_upstream_auto">Auto detect system VPN</string>

View File

@@ -35,6 +35,14 @@
android:icon="@drawable/ic_image_looks_6" android:icon="@drawable/ic_image_looks_6"
android:title="@string/settings_service_disable_ipv6" android:title="@string/settings_service_disable_ipv6"
android:summary="@string/settings_service_disable_ipv6_summary"/> android:summary="@string/settings_service_disable_ipv6_summary"/>
<SimpleMenuPreference
android:key="service.wifiLock"
android:icon="@drawable/ic_device_wifi_lock"
android:entries="@array/settings_service_wifi_lock"
android:entryValues="@array/settings_service_wifi_lock_values"
android:defaultValue="Full"
android:summary="%s"
android:title="@string/settings_service_wifi_lock"/>
<AutoSummaryEditTextPreference <AutoSummaryEditTextPreference
android:key="service.dns" android:key="service.dns"
android:icon="@drawable/ic_action_dns" android:icon="@drawable/ic_action_dns"