diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt new file mode 100644 index 00000000..0e457988 --- /dev/null +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/IpNeighbourMonitoringTileService.kt @@ -0,0 +1,38 @@ +package be.mygod.vpnhotspot.manage + +import android.service.quicksettings.Tile +import androidx.annotation.RequiresApi +import be.mygod.vpnhotspot.R +import be.mygod.vpnhotspot.net.IpNeighbour +import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor +import be.mygod.vpnhotspot.util.KillableTileService + +@RequiresApi(24) +abstract class IpNeighbourMonitoringTileService : KillableTileService(), IpNeighbourMonitor.Callback { + private var neighbours: Collection = emptyList() + abstract fun updateTile() + + override fun onStartListening() { + super.onStartListening() + IpNeighbourMonitor.registerCallback(this) + } + + override fun onStopListening() { + IpNeighbourMonitor.unregisterCallback(this) + super.onStopListening() + } + + protected fun Tile.subtitleDevices(filter: (String) -> Boolean) { + val size = neighbours + .filter { it.state != IpNeighbour.State.FAILED && filter(it.dev) } + .distinctBy { it.lladdr } + .size + if (size > 0) subtitle(resources.getQuantityString( + R.plurals.quick_settings_hotspot_secondary_label_num_devices, size, size)) + } + + override fun onIpNeighbourAvailable(neighbours: Collection) { + this.neighbours = neighbours + updateTile() + } +} diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt index 4645b8a9..1469d46c 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/LocalOnlyHotspotTileService.kt @@ -9,11 +9,10 @@ import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import be.mygod.vpnhotspot.LocalOnlyHotspotService import be.mygod.vpnhotspot.R -import be.mygod.vpnhotspot.util.KillableTileService import be.mygod.vpnhotspot.util.stopAndUnbind @RequiresApi(26) -class LocalOnlyHotspotTileService : KillableTileService() { +class LocalOnlyHotspotTileService : IpNeighbourMonitoringTileService() { private val tile by lazy { Icon.createWithResource(application, R.drawable.ic_action_perm_scan_wifi) } private var binder: LocalOnlyHotspotService.Binder? = null @@ -28,6 +27,24 @@ class LocalOnlyHotspotTileService : KillableTileService() { super.onStopListening() } + override fun updateTile() { + val binder = binder ?: return + qsTile?.run { + icon = tile + subtitle(null) + val iface = binder.iface + if (iface.isNullOrEmpty()) { + state = Tile.STATE_INACTIVE + label = getText(R.string.tethering_temp_hotspot) + } else { + state = Tile.STATE_ACTIVE + label = binder.configuration?.ssid ?: getText(R.string.tethering_temp_hotspot) + subtitleDevices { it == iface } + } + updateTile() + } + } + override fun onClick() { val binder = binder when { @@ -39,19 +56,7 @@ class LocalOnlyHotspotTileService : KillableTileService() { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { binder = service as LocalOnlyHotspotService.Binder - service.ifaceChanged[this] = { - qsTile?.run { - icon = tile - if (it.isNullOrEmpty()) { - state = Tile.STATE_INACTIVE - label = getText(R.string.tethering_temp_hotspot) - } else { - state = Tile.STATE_ACTIVE - label = service.configuration?.ssid ?: getText(R.string.tethering_temp_hotspot) - } - updateTile() - } - } + service.ifaceChanged[this] = { updateTile() } super.onServiceConnected(name, service) } 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 469eb63c..1b1a7d76 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -17,7 +17,6 @@ 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 @@ -26,14 +25,14 @@ import java.io.IOException import java.lang.reflect.InvocationTargetException @RequiresApi(24) -sealed class TetheringTileService : KillableTileService(), TetheringManager.StartTetheringCallback { +sealed class TetheringTileService : IpNeighbourMonitoringTileService(), TetheringManager.StartTetheringCallback { 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 + private var tethered: List? = null protected val interested get() = tethered?.filter { TetherType.ofInterface(it) == tetherType } protected var binder: TetheringService.Binder? = null @@ -73,8 +72,9 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star binder = null } - protected open fun updateTile() { + override fun updateTile() { qsTile?.run { + subtitle(null) val interested = interested when { interested == null -> { @@ -89,6 +89,7 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star val binder = binder ?: return state = Tile.STATE_ACTIVE icon = if (interested.all(binder::isActive)) tileOn else tileOff + subtitleDevices(interested::contains) } } label = getText(labelString) @@ -172,6 +173,7 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star override fun updateTile() { qsTile?.run { + subtitle(null) val interested = interested if (interested == null) { state = Tile.STATE_UNAVAILABLE @@ -181,6 +183,7 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star val binder = binder ?: return state = Tile.STATE_ACTIVE icon = if (interested.isNotEmpty() && interested.all(binder::isActive)) tileOn else tileOff + subtitleDevices(interested::contains) } false -> { state = Tile.STATE_INACTIVE 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 2cf1d6d1..796b8e6d 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetherType.kt @@ -46,7 +46,8 @@ enum class TetherType(@DrawableRes val icon: Int) { .map { it.toPattern() } @RequiresApi(30) - private fun updateRegexs() { + private fun updateRegexs() = synchronized(this) { + if (!requiresUpdate) return@synchronized requiresUpdate = false TetheringManager.registerTetheringEventCallback(null, this) val tethering = "com.android.networkstack.tethering" to app.packageManager.getResourcesForApplication( @@ -59,7 +60,8 @@ enum class TetherType(@DrawableRes val icon: Int) { } @RequiresApi(30) - override fun onTetherableInterfaceRegexpsChanged(args: Array?) { + override fun onTetherableInterfaceRegexpsChanged(args: Array?) = synchronized(this) { + if (requiresUpdate) return@synchronized Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentToString()}") TetheringManager.unregisterTetheringEventCallback(this) requiresUpdate = true @@ -92,6 +94,7 @@ enum class TetherType(@DrawableRes val icon: Int) { iface == null -> NONE iface == p2pDev -> WIFI_P2P requiresUpdate -> { + Timber.d("requiresUpdate") if (Build.VERSION.SDK_INT >= 30) updateRegexs() else error("unexpected requiresUpdate") ofInterface(iface, p2pDev) }