Add a lot more QS tiles

Fix #53. Also fix Bluetooth connection leaks.
This commit is contained in:
Mygod
2018-12-25 11:37:36 +08:00
parent 365287202e
commit 426b93226d
15 changed files with 481 additions and 163 deletions

View File

@@ -0,0 +1,174 @@
package be.mygod.vpnhotspot.manage
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.drawable.Icon
import android.os.IBinder
import android.service.quicksettings.Tile
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
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.wifi.WifiApManager
import be.mygod.vpnhotspot.util.stopAndUnbind
import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber
import java.io.IOException
import java.lang.reflect.InvocationTargetException
@RequiresApi(24)
sealed class TetheringTileService : TetherListeningTileService(), ServiceConnection,
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 val interested get() = tethered.filter { TetherType.ofInterface(it) == tetherType }
protected var binder: TetheringService.Binder? = null
protected abstract fun start()
protected abstract fun stop()
override fun onStartListening() {
bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE)
super.onStartListening()
}
override fun onStopListening() {
super.onStopListening()
stopAndUnbind(this)
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = service as TetheringService.Binder
service.routingsChanged[this] = { updateTile() }
}
override fun onServiceDisconnected(name: ComponentName?) {
binder = null
}
override fun updateTile() {
qsTile?.run {
val interested = interested
if (interested.isEmpty()) {
state = Tile.STATE_INACTIVE
icon = tileOff
} else {
state = Tile.STATE_ACTIVE
icon = if (interested.all { binder?.isActive(it) == true }) tileOn else tileOff
}
label = getText(labelString)
updateTile()
}
}
protected inline fun safeInvoker(func: () -> Unit) = try {
func()
} catch (e: IOException) {
Timber.w(e)
Toast.makeText(this, e.localizedMessage, Toast.LENGTH_LONG).show()
} catch (e: InvocationTargetException) {
if (e.targetException !is SecurityException) Timber.w(e)
var cause: Throwable? = e
while (cause != null) {
cause = cause.cause
if (cause != null && cause !is InvocationTargetException) {
Toast.makeText(this, cause.localizedMessage, Toast.LENGTH_LONG).show()
break
}
}
}
override fun onClick() {
val interested = interested
if (interested.isEmpty()) safeInvoker { start() } else {
val inactive = interested.filter { binder?.isActive(it) != true }
if (inactive.isEmpty()) safeInvoker { stop() }
else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
}
}
override fun onTetheringStarted() = updateTile()
override fun onTetheringFailed() = SmartSnackbar.make(R.string.tethering_manage_failed).show()
class Wifi : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_wifi
override val tetherType get() = TetherType.WIFI
override val icon get() = R.drawable.ic_device_wifi_tethering
override fun start() = TetheringManager.start(TetheringManager.TETHERING_WIFI, true, this)
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_WIFI)
}
class Usb : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_usb
override val tetherType get() = TetherType.USB
override fun start() = TetheringManager.start(TetheringManager.TETHERING_USB, true, this)
override fun stop() = TetheringManager.stop(TetheringManager.TETHERING_USB)
}
class Bluetooth : TetheringTileService() {
private var tethering: BluetoothTethering? = null
override val labelString get() = R.string.tethering_manage_bluetooth
override val tetherType get() = TetherType.BLUETOOTH
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this)
override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
Thread.sleep(1) // give others a room to breathe
onTetheringStarted() // force flush state
}
override fun onStartListening() {
tethering = BluetoothTethering(this)
super.onStartListening()
}
override fun onStopListening() {
super.onStopListening()
tethering?.close()
tethering = null
}
override fun updateTile() {
qsTile?.run {
if (tethering?.active == true) {
state = Tile.STATE_ACTIVE
val interested = interested
icon = if (interested.isNotEmpty() && interested.all { binder?.isActive(it) == true })
tileOn else tileOff
} else {
state = Tile.STATE_INACTIVE
icon = tileOff
}
label = getText(labelString)
updateTile()
}
}
override fun onClick() = if (tethering?.active == true) {
val inactive = interested.filter { binder?.isActive(it) != true }
if (inactive.isEmpty()) safeInvoker { stop() }
else ContextCompat.startForegroundService(this, Intent(this, TetheringService::class.java)
.putExtra(TetheringService.EXTRA_ADD_INTERFACES, inactive.toTypedArray()))
} else safeInvoker { start() }
}
@Suppress("DEPRECATION")
@Deprecated("Not usable since API 26")
class WifiLegacy : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_wifi_legacy
override val tetherType get() = TetherType.WIFI
override val icon get() = R.drawable.ic_device_wifi_tethering
override fun start() = WifiApManager.start()
override fun stop() = WifiApManager.stop()
}
}