Merge branch 'master' into r
This commit is contained in:
@@ -12,12 +12,12 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
classpath(kotlin("gradle-plugin", kotlinVersion))
|
||||||
classpath("com.android.tools.build:gradle:4.0.0")
|
classpath("com.android.tools.build:gradle:4.0.0")
|
||||||
classpath("com.github.ben-manes:gradle-versions-plugin:0.28.0")
|
classpath("com.github.ben-manes:gradle-versions-plugin:0.28.0")
|
||||||
classpath("com.google.firebase:firebase-crashlytics-gradle:2.1.1")
|
classpath("com.google.firebase:firebase-crashlytics-gradle:2.1.1")
|
||||||
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
|
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
|
||||||
classpath("com.google.gms:google-services:4.3.3")
|
classpath("com.google.gms:google-services:4.3.3")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
|
||||||
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
|
||||||
import be.mygod.vpnhotspot.util.KillableTileService
|
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
|
||||||
|
|
||||||
@RequiresApi(24)
|
|
||||||
abstract class TetherListeningTileService : KillableTileService() {
|
|
||||||
protected var tethered: List<String>? = null
|
|
||||||
|
|
||||||
private val receiver = broadcastReceiver { _, intent ->
|
|
||||||
tethered = intent.tetheredIfaces ?: return@broadcastReceiver
|
|
||||||
updateTile()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartListening() {
|
|
||||||
super.onStartListening()
|
|
||||||
tethered = registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
|
||||||
?.tetheredIfaces
|
|
||||||
updateTile()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStopListening() {
|
|
||||||
unregisterReceiver(receiver)
|
|
||||||
super.onStopListening()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract fun updateTile()
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.manage
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
@@ -13,7 +14,10 @@ import be.mygod.vpnhotspot.R
|
|||||||
import be.mygod.vpnhotspot.TetheringService
|
import be.mygod.vpnhotspot.TetheringService
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
|
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.util.KillableTileService
|
||||||
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
import be.mygod.vpnhotspot.util.readableMessage
|
import be.mygod.vpnhotspot.util.readableMessage
|
||||||
import be.mygod.vpnhotspot.util.stopAndUnbind
|
import be.mygod.vpnhotspot.util.stopAndUnbind
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -21,27 +25,37 @@ import java.io.IOException
|
|||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
sealed class TetheringTileService : TetherListeningTileService(), TetheringManager.StartTetheringCallback {
|
sealed class TetheringTileService : KillableTileService(), TetheringManager.StartTetheringCallback {
|
||||||
protected val tileOff by lazy { Icon.createWithResource(application, icon) }
|
protected val tileOff by lazy { Icon.createWithResource(application, icon) }
|
||||||
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
|
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
|
||||||
|
|
||||||
protected abstract val labelString: Int
|
protected abstract val labelString: Int
|
||||||
protected abstract val tetherType: TetherType
|
protected abstract val tetherType: TetherType
|
||||||
protected open val icon get() = tetherType.icon
|
protected open val icon get() = tetherType.icon
|
||||||
|
protected var tethered: List<String>? = null
|
||||||
protected val interested get() = tethered?.filter { TetherType.ofInterface(it) == tetherType }
|
protected val interested get() = tethered?.filter { TetherType.ofInterface(it) == tetherType }
|
||||||
protected var binder: TetheringService.Binder? = null
|
protected var binder: TetheringService.Binder? = null
|
||||||
|
|
||||||
|
private val receiver = broadcastReceiver { _, intent ->
|
||||||
|
tethered = intent.tetheredIfaces ?: return@broadcastReceiver
|
||||||
|
updateTile()
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract fun start()
|
protected abstract fun start()
|
||||||
protected abstract fun stop()
|
protected abstract fun stop()
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE)
|
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
|
bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE)
|
||||||
|
tethered = registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
||||||
|
?.tetheredIfaces
|
||||||
|
updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
super.onStopListening()
|
unregisterReceiver(receiver)
|
||||||
stopAndUnbind(this)
|
stopAndUnbind(this)
|
||||||
|
super.onStopListening()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
@@ -55,7 +69,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
|
|||||||
binder = null
|
binder = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateTile() {
|
protected open fun updateTile() {
|
||||||
qsTile?.run {
|
qsTile?.run {
|
||||||
val interested = interested
|
val interested = interested
|
||||||
when {
|
when {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.net.monitor
|
package be.mygod.vpnhotspot.net.monitor
|
||||||
|
|
||||||
import android.util.LongSparseArray
|
import android.util.LongSparseArray
|
||||||
import androidx.core.os.postDelayed
|
|
||||||
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES
|
||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
@@ -9,6 +8,7 @@ import be.mygod.vpnhotspot.util.Event2
|
|||||||
import be.mygod.vpnhotspot.util.RootSession
|
import be.mygod.vpnhotspot.util.RootSession
|
||||||
import be.mygod.vpnhotspot.util.parseNumericAddress
|
import be.mygod.vpnhotspot.util.parseNumericAddress
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -16,7 +16,6 @@ import java.util.concurrent.TimeUnit
|
|||||||
object TrafficRecorder {
|
object TrafficRecorder {
|
||||||
private const val ANYWHERE = "0.0.0.0/0"
|
private const val ANYWHERE = "0.0.0.0/0"
|
||||||
|
|
||||||
private var scheduled = false
|
|
||||||
private var lastUpdate = 0L
|
private var lastUpdate = 0L
|
||||||
private val records = mutableMapOf<Pair<InetAddress, String>, TrafficRecord>()
|
private val records = mutableMapOf<Pair<InetAddress, String>, TrafficRecord>()
|
||||||
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
|
val foregroundListeners = Event2<Collection<TrafficRecord>, LongSparseArray<TrafficRecord>>()
|
||||||
@@ -36,18 +35,21 @@ object TrafficRecorder {
|
|||||||
if (records.remove(Pair(ip, downstream)) == null) Timber.w("Failed to find traffic record for $ip%$downstream.")
|
if (records.remove(Pair(ip, downstream)) == null) Timber.w("Failed to find traffic record for $ip%$downstream.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var updateJob: Job? = null
|
||||||
private fun unscheduleUpdateLocked() {
|
private fun unscheduleUpdateLocked() {
|
||||||
RootSession.handler.removeCallbacksAndMessages(this)
|
updateJob?.cancel()
|
||||||
scheduled = false
|
updateJob = null
|
||||||
}
|
}
|
||||||
private fun scheduleUpdateLocked() {
|
private fun scheduleUpdateLocked() {
|
||||||
if (scheduled) return
|
if (updateJob != null) return
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val minute = TimeUnit.MINUTES.toMillis(1)
|
val minute = TimeUnit.MINUTES.toMillis(1)
|
||||||
var timeout = minute - now % minute
|
var timeout = minute - now % minute
|
||||||
if (foregroundListeners.isNotEmpty() && timeout > 1000) timeout = 1000
|
if (foregroundListeners.isNotEmpty() && timeout > 1000) timeout = 1000
|
||||||
RootSession.handler.postDelayed(timeout, this) { update(true) }
|
updateJob = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
scheduled = true
|
delay(timeout)
|
||||||
|
update(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rescheduleUpdate() = synchronized(this) {
|
fun rescheduleUpdate() = synchronized(this) {
|
||||||
@@ -122,7 +124,6 @@ object TrafficRecorder {
|
|||||||
}
|
}
|
||||||
fun update(timeout: Boolean = false) {
|
fun update(timeout: Boolean = false) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (timeout) scheduled = false
|
|
||||||
if (records.isEmpty()) return
|
if (records.isEmpty()) return
|
||||||
val timestamp = System.currentTimeMillis()
|
val timestamp = System.currentTimeMillis()
|
||||||
if (!timeout && timestamp - lastUpdate <= 100) return
|
if (!timeout && timestamp - lastUpdate <= 100) return
|
||||||
@@ -133,6 +134,7 @@ object TrafficRecorder {
|
|||||||
SmartSnackbar.make(e).show()
|
SmartSnackbar.make(e).show()
|
||||||
}
|
}
|
||||||
lastUpdate = timestamp
|
lastUpdate = timestamp
|
||||||
|
updateJob = null
|
||||||
scheduleUpdateLocked()
|
scheduleUpdateLocked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ object VpnMonitor : UpstreamMonitor() {
|
|||||||
when (ifname) {
|
when (ifname) {
|
||||||
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
null -> Timber.w("interfaceName became null: $oldProperties -> $properties")
|
||||||
oldProperties.interfaceName -> losing = false
|
oldProperties.interfaceName -> losing = false
|
||||||
else -> Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties"))
|
else -> Timber.w("interfaceName changed: $oldProperties -> $properties")
|
||||||
}
|
}
|
||||||
callbacks.toList()
|
callbacks.toList()
|
||||||
}.forEach {
|
}.forEach {
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ object WifiP2pManagerHelper {
|
|||||||
arrayOf(interfacePersistentGroupInfoListener)) { proxy, method, args ->
|
arrayOf(interfacePersistentGroupInfoListener)) { proxy, method, args ->
|
||||||
if (method.name == "onPersistentGroupInfoAvailable") {
|
if (method.name == "onPersistentGroupInfoAvailable") {
|
||||||
if (args.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
|
if (args.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
listener(getGroupList.invoke(args[0]) as Collection<WifiP2pGroup>)
|
listener(getGroupList.invoke(args[0]) as Collection<WifiP2pGroup>)
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package be.mygod.vpnhotspot.util
|
package be.mygod.vpnhotspot.util
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.os.postDelayed
|
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -15,13 +13,9 @@ import kotlin.concurrent.withLock
|
|||||||
|
|
||||||
class RootSession : AutoCloseable {
|
class RootSession : AutoCloseable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "RootSession"
|
|
||||||
|
|
||||||
val handler = Handler(HandlerThread("$TAG-HandlerThread").apply { start() }.looper)
|
|
||||||
|
|
||||||
private val monitor = ReentrantLock()
|
private val monitor = ReentrantLock()
|
||||||
private fun onUnlock() {
|
private fun onUnlock() {
|
||||||
if (monitor.holdCount == 1) instance?.startTimeout()
|
if (monitor.holdCount == 1) instance?.startTimeoutLocked()
|
||||||
}
|
}
|
||||||
private fun unlock() {
|
private fun unlock() {
|
||||||
onUnlock()
|
onUnlock()
|
||||||
@@ -36,7 +30,7 @@ class RootSession : AutoCloseable {
|
|||||||
}
|
}
|
||||||
fun <T> use(operation: (RootSession) -> T) = monitor.withLock {
|
fun <T> use(operation: (RootSession) -> T) = monitor.withLock {
|
||||||
val instance = ensureInstance()
|
val instance = ensureInstance()
|
||||||
instance.haltTimeout()
|
instance.haltTimeoutLocked()
|
||||||
operation(instance).also { onUnlock() }
|
operation(instance).also { onUnlock() }
|
||||||
}
|
}
|
||||||
fun beginTransaction(): Transaction {
|
fun beginTransaction(): Transaction {
|
||||||
@@ -47,14 +41,14 @@ class RootSession : AutoCloseable {
|
|||||||
unlock()
|
unlock()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
instance.haltTimeout()
|
instance.haltTimeoutLocked()
|
||||||
return instance.Transaction()
|
return instance.Transaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun trimMemory() = monitor.withLock {
|
fun trimMemory() = monitor.withLock {
|
||||||
val instance = instance ?: return
|
val instance = instance ?: return
|
||||||
instance.haltTimeout()
|
instance.haltTimeoutLocked()
|
||||||
instance.close()
|
instance.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +79,22 @@ class RootSession : AutoCloseable {
|
|||||||
shell.close()
|
shell.close()
|
||||||
if (instance == this) instance = null
|
if (instance == this) instance = null
|
||||||
}
|
}
|
||||||
private fun startTimeout() = handler.postDelayed(TimeUnit.MINUTES.toMillis(5), this) {
|
|
||||||
monitor.withLock { close() }
|
private var timeoutJob: Job? = null
|
||||||
|
private fun startTimeoutLocked() {
|
||||||
|
check(timeoutJob == null)
|
||||||
|
timeoutJob = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
|
delay(TimeUnit.MINUTES.toMillis(5))
|
||||||
|
monitor.withLock {
|
||||||
|
close()
|
||||||
|
timeoutJob = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun haltTimeoutLocked() {
|
||||||
|
timeoutJob?.cancel()
|
||||||
|
timeoutJob = null
|
||||||
}
|
}
|
||||||
private fun haltTimeout() = handler.removeCallbacksAndMessages(this)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't care about the results, but still sync.
|
* Don't care about the results, but still sync.
|
||||||
@@ -141,7 +147,7 @@ class RootSession : AutoCloseable {
|
|||||||
locked = true
|
locked = true
|
||||||
ensureInstance()
|
ensureInstance()
|
||||||
}
|
}
|
||||||
shell.haltTimeout()
|
shell.haltTimeoutLocked()
|
||||||
revertCommands.forEach { shell.submit(it) }
|
revertCommands.forEach { shell.submit(it) }
|
||||||
} catch (e: RuntimeException) { // if revert fails, it should fail silently
|
} catch (e: RuntimeException) { // if revert fails, it should fail silently
|
||||||
Timber.d(e)
|
Timber.d(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user