Support Wi-Fi keep alives
This commit is contained in:
@@ -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'
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
13
mobile/src/main/res/values/arrays.xml
Normal file
13
mobile/src/main/res/values/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_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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user