mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-07-03 14:05:13 +02:00
0295abd808
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
188 lines
7.1 KiB
Kotlin
188 lines
7.1 KiB
Kotlin
/*
|
|
* Copyright © 2017-2025 WireGuard LLC. All Rights Reserved.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
package com.wireguard.android
|
|
|
|
import android.app.PendingIntent
|
|
import android.content.Intent
|
|
import android.graphics.Bitmap
|
|
import android.graphics.Canvas
|
|
import android.graphics.drawable.Icon
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.IBinder
|
|
import android.provider.Settings
|
|
import android.service.quicksettings.Tile
|
|
import android.service.quicksettings.TileService
|
|
import android.util.Log
|
|
import androidx.annotation.RequiresApi
|
|
import androidx.databinding.Observable
|
|
import androidx.databinding.Observable.OnPropertyChangedCallback
|
|
import com.wireguard.android.activity.MainActivity
|
|
import com.wireguard.android.activity.TunnelToggleActivity
|
|
import com.wireguard.android.backend.Tunnel
|
|
import com.wireguard.android.model.ObservableTunnel
|
|
import com.wireguard.android.util.applicationScope
|
|
import com.wireguard.android.widget.SlashDrawable
|
|
import kotlinx.coroutines.launch
|
|
|
|
/**
|
|
* Service that maintains the application's custom Quick Settings tile. This service is bound by the
|
|
* system framework as necessary to update the appearance of the tile in the system UI, and to
|
|
* forward click events to the application.
|
|
*/
|
|
@RequiresApi(Build.VERSION_CODES.N)
|
|
class QuickTileService : TileService() {
|
|
private val onStateChangedCallback = OnStateChangedCallback()
|
|
private val onTunnelChangedCallback = OnTunnelChangedCallback()
|
|
private var iconOff: Icon? = null
|
|
private var iconOn: Icon? = null
|
|
private var tunnel: ObservableTunnel? = null
|
|
|
|
/* This works around an annoying unsolved frameworks bug some people are hitting. */
|
|
override fun onBind(intent: Intent): IBinder? {
|
|
var ret: IBinder? = null
|
|
try {
|
|
ret = super.onBind(intent)
|
|
} catch (e: Throwable) {
|
|
Log.d(TAG, "Failed to bind to TileService", e)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
override fun onClick() {
|
|
when (val tunnel = tunnel) {
|
|
null -> {
|
|
val intent = Intent(this, MainActivity::class.java)
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE))
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
startActivityAndCollapse(intent)
|
|
}
|
|
}
|
|
else -> {
|
|
unlockAndRun {
|
|
applicationScope.launch {
|
|
try {
|
|
tunnel.setStateAsync(Tunnel.State.TOGGLE)
|
|
updateTile()
|
|
} catch (_: Throwable) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !Settings.canDrawOverlays(this@QuickTileService)) {
|
|
val permissionIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
|
|
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
startActivityAndCollapse(PendingIntent.getActivity(this@QuickTileService, 0, permissionIntent, PendingIntent.FLAG_IMMUTABLE))
|
|
return@launch
|
|
}
|
|
val toggleIntent = Intent(this@QuickTileService, TunnelToggleActivity::class.java)
|
|
toggleIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
startActivity(toggleIntent)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onCreate() {
|
|
isAdded = true
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
iconOn = Icon.createWithResource(this, R.drawable.ic_tile)
|
|
iconOff = iconOn
|
|
return
|
|
}
|
|
val icon = SlashDrawable(resources.getDrawable(R.drawable.ic_tile, Application.get().theme))
|
|
icon.setAnimationEnabled(false) /* Unfortunately we can't have animations, since Icons are marshaled. */
|
|
icon.setSlashed(false)
|
|
var b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
|
var c = Canvas(b)
|
|
icon.setBounds(0, 0, c.width, c.height)
|
|
icon.draw(c)
|
|
iconOn = Icon.createWithBitmap(b)
|
|
icon.setSlashed(true)
|
|
b = Bitmap.createBitmap(icon.intrinsicWidth, icon.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
|
c = Canvas(b)
|
|
icon.setBounds(0, 0, c.width, c.height)
|
|
icon.draw(c)
|
|
iconOff = Icon.createWithBitmap(b)
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
isAdded = false
|
|
}
|
|
|
|
override fun onStartListening() {
|
|
Application.getTunnelManager().addOnPropertyChangedCallback(onTunnelChangedCallback)
|
|
tunnel?.addOnPropertyChangedCallback(onStateChangedCallback)
|
|
updateTile()
|
|
}
|
|
|
|
override fun onStopListening() {
|
|
tunnel?.removeOnPropertyChangedCallback(onStateChangedCallback)
|
|
Application.getTunnelManager().removeOnPropertyChangedCallback(onTunnelChangedCallback)
|
|
}
|
|
|
|
override fun onTileAdded() {
|
|
isAdded = true
|
|
}
|
|
|
|
override fun onTileRemoved() {
|
|
isAdded = false
|
|
}
|
|
|
|
private fun updateTile() {
|
|
// Update the tunnel.
|
|
val newTunnel = Application.getTunnelManager().lastUsedTunnel
|
|
if (newTunnel != tunnel) {
|
|
tunnel?.removeOnPropertyChangedCallback(onStateChangedCallback)
|
|
tunnel = newTunnel
|
|
tunnel?.addOnPropertyChangedCallback(onStateChangedCallback)
|
|
}
|
|
// Update the tile contents.
|
|
val tile = qsTile ?: return
|
|
|
|
when (val tunnel = tunnel) {
|
|
null -> {
|
|
tile.label = getString(R.string.app_name)
|
|
tile.state = Tile.STATE_INACTIVE
|
|
tile.icon = iconOff
|
|
}
|
|
else -> {
|
|
tile.label = tunnel.name
|
|
tile.state = if (tunnel.state == Tunnel.State.UP) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
|
tile.icon = if (tunnel.state == Tunnel.State.UP) iconOn else iconOff
|
|
}
|
|
}
|
|
tile.updateTile()
|
|
}
|
|
|
|
private inner class OnStateChangedCallback : OnPropertyChangedCallback() {
|
|
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
|
|
if (sender != tunnel) {
|
|
sender.removeOnPropertyChangedCallback(this)
|
|
return
|
|
}
|
|
if (propertyId != 0 && propertyId != BR.state)
|
|
return
|
|
updateTile()
|
|
}
|
|
}
|
|
|
|
private inner class OnTunnelChangedCallback : OnPropertyChangedCallback() {
|
|
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
|
|
if (propertyId != 0 && propertyId != BR.lastUsedTunnel)
|
|
return
|
|
updateTile()
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val TAG = "WireGuard/QuickTileService"
|
|
var isAdded: Boolean = false
|
|
private set
|
|
}
|
|
}
|