From 677b2d8f8e8847390e7f42a3191fd5f6281b6fac Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 29 May 2020 01:12:00 -0400 Subject: [PATCH 1/5] Refine build gradle file --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2e49fb45..7ba90adc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,12 +12,12 @@ buildscript { } dependencies { + classpath(kotlin("gradle-plugin", kotlinVersion)) classpath("com.android.tools.build:gradle:4.0.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.android.gms:oss-licenses-plugin:0.10.2") classpath("com.google.gms:google-services:4.3.3") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } From 8307f9f8d4c0147acf1f99afd48bd53ae4caffa9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 29 May 2020 01:18:07 -0400 Subject: [PATCH 2/5] Suppress reporting VpnMonitor interfaceName changed --- .../src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt index 5a203bc6..91cdc953 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/VpnMonitor.kt @@ -51,7 +51,7 @@ object VpnMonitor : UpstreamMonitor() { when (ifname) { null -> Timber.w("interfaceName became null: $oldProperties -> $properties") oldProperties.interfaceName -> losing = false - else -> Timber.w(RuntimeException("interfaceName changed: $oldProperties -> $properties")) + else -> Timber.w("interfaceName changed: $oldProperties -> $properties") } callbacks.toList() }.forEach { From df1ef04d18176433f82d397e23a4c8b83bc8e21d Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 29 May 2020 01:38:02 -0400 Subject: [PATCH 3/5] Migrate to coroutines instead of Handler --- .../vpnhotspot/net/monitor/TrafficRecorder.kt | 18 +++++----- .../be/mygod/vpnhotspot/util/RootSession.kt | 36 +++++++++++-------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt index ab218689..bfecd37e 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/monitor/TrafficRecorder.kt @@ -1,7 +1,6 @@ package be.mygod.vpnhotspot.net.monitor import android.util.LongSparseArray -import androidx.core.os.postDelayed import be.mygod.vpnhotspot.net.Routing.Companion.IPTABLES import be.mygod.vpnhotspot.room.AppDatabase 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.parseNumericAddress import be.mygod.vpnhotspot.widget.SmartSnackbar +import kotlinx.coroutines.* import timber.log.Timber import java.net.InetAddress import java.util.concurrent.TimeUnit @@ -16,7 +16,6 @@ import java.util.concurrent.TimeUnit object TrafficRecorder { private const val ANYWHERE = "0.0.0.0/0" - private var scheduled = false private var lastUpdate = 0L private val records = mutableMapOf, TrafficRecord>() val foregroundListeners = Event2, LongSparseArray>() @@ -36,18 +35,21 @@ object TrafficRecorder { 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() { - RootSession.handler.removeCallbacksAndMessages(this) - scheduled = false + updateJob?.cancel() + updateJob = null } private fun scheduleUpdateLocked() { - if (scheduled) return + if (updateJob != null) return val now = System.currentTimeMillis() val minute = TimeUnit.MINUTES.toMillis(1) var timeout = minute - now % minute if (foregroundListeners.isNotEmpty() && timeout > 1000) timeout = 1000 - RootSession.handler.postDelayed(timeout, this) { update(true) } - scheduled = true + updateJob = GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { + delay(timeout) + update(true) + } } fun rescheduleUpdate() = synchronized(this) { @@ -122,7 +124,6 @@ object TrafficRecorder { } fun update(timeout: Boolean = false) { synchronized(this) { - if (timeout) scheduled = false if (records.isEmpty()) return val timestamp = System.currentTimeMillis() if (!timeout && timestamp - lastUpdate <= 100) return @@ -133,6 +134,7 @@ object TrafficRecorder { SmartSnackbar.make(e).show() } lastUpdate = timestamp + updateJob = null scheduleUpdateLocked() } } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt index cf82551c..25ac9a51 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/util/RootSession.kt @@ -1,11 +1,9 @@ package be.mygod.vpnhotspot.util -import android.os.Handler -import android.os.HandlerThread import android.os.Looper import androidx.annotation.WorkerThread -import androidx.core.os.postDelayed import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.* import timber.log.Timber import java.util.* import java.util.concurrent.TimeUnit @@ -15,13 +13,9 @@ import kotlin.concurrent.withLock class RootSession : AutoCloseable { companion object { - private const val TAG = "RootSession" - - val handler = Handler(HandlerThread("$TAG-HandlerThread").apply { start() }.looper) - private val monitor = ReentrantLock() private fun onUnlock() { - if (monitor.holdCount == 1) instance?.startTimeout() + if (monitor.holdCount == 1) instance?.startTimeoutLocked() } private fun unlock() { onUnlock() @@ -36,7 +30,7 @@ class RootSession : AutoCloseable { } fun use(operation: (RootSession) -> T) = monitor.withLock { val instance = ensureInstance() - instance.haltTimeout() + instance.haltTimeoutLocked() operation(instance).also { onUnlock() } } fun beginTransaction(): Transaction { @@ -47,14 +41,14 @@ class RootSession : AutoCloseable { unlock() throw e } - instance.haltTimeout() + instance.haltTimeoutLocked() return instance.Transaction() } @WorkerThread fun trimMemory() = monitor.withLock { val instance = instance ?: return - instance.haltTimeout() + instance.haltTimeoutLocked() instance.close() } @@ -85,10 +79,22 @@ class RootSession : AutoCloseable { shell.close() 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. @@ -141,7 +147,7 @@ class RootSession : AutoCloseable { locked = true ensureInstance() } - shell.haltTimeout() + shell.haltTimeoutLocked() revertCommands.forEach { shell.submit(it) } } catch (e: RuntimeException) { // if revert fails, it should fail silently Timber.d(e) From 6ee1bf3666d9be940372f9f4495025cabc3f53bc Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 29 May 2020 01:55:29 -0400 Subject: [PATCH 4/5] Suppress UNCHECKED_CAST --- .../java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt index e78cbf5d..50919009 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/wifi/WifiP2pManagerHelper.kt @@ -93,6 +93,7 @@ object WifiP2pManagerHelper { arrayOf(interfacePersistentGroupInfoListener)) { proxy, method, args -> if (method.name == "onPersistentGroupInfoAvailable") { if (args.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args")) + @Suppress("UNCHECKED_CAST") listener(getGroupList.invoke(args[0]) as Collection) null } else { From 803863065ac571f9bdae5dd16da17117f0a74cc4 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 29 May 2020 20:41:07 -0400 Subject: [PATCH 5/5] Remove unnecessary abstraction --- .../manage/TetherListeningTileService.kt | 32 ------------------- .../vpnhotspot/manage/TetheringTileService.kt | 22 ++++++++++--- 2 files changed, 18 insertions(+), 36 deletions(-) delete mode 100644 mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt deleted file mode 100644 index 49ffff08..00000000 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt +++ /dev/null @@ -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? = 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() -} diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index 983378f0..55bb7b44 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.manage import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.graphics.drawable.Icon import android.os.IBinder import android.service.quicksettings.Tile @@ -13,7 +14,10 @@ import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.TetheringService import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetheringManager +import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces 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.stopAndUnbind import timber.log.Timber @@ -21,27 +25,37 @@ import java.io.IOException import java.lang.reflect.InvocationTargetException @RequiresApi(24) -sealed class TetheringTileService : TetherListeningTileService(), TetheringManager.OnStartTetheringCallback { +sealed class TetheringTileService : KillableTileService(), TetheringManager.OnStartTetheringCallback { 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 abstract val labelString: Int protected abstract val tetherType: TetherType protected open val icon get() = tetherType.icon + protected var tethered: List? = null protected val interested get() = tethered?.filter { TetherType.ofInterface(it) == tetherType } protected var binder: TetheringService.Binder? = null + private val receiver = broadcastReceiver { _, intent -> + tethered = intent.tetheredIfaces ?: return@broadcastReceiver + updateTile() + } + protected abstract fun start() protected abstract fun stop() override fun onStartListening() { - bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE) 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() { - super.onStopListening() + unregisterReceiver(receiver) stopAndUnbind(this) + super.onStopListening() } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { @@ -55,7 +69,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag binder = null } - override fun updateTile() { + protected open fun updateTile() { qsTile?.run { val interested = interested when {