Android notification and routing (#797)
Android notification and routing
This commit is contained in:
parent
ff348a348c
commit
abb3c918e3
40 changed files with 1108 additions and 543 deletions
180
client/android/src/org/amnezia/vpn/ServiceNotification.kt
Normal file
180
client/android/src/org/amnezia/vpn/ServiceNotification.kt
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat.Builder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.Action
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.TrafficStats.TrafficData
|
||||
|
||||
private const val TAG = "ServiceNotification"
|
||||
|
||||
private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
|
||||
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
||||
const val NOTIFICATION_ID = 1337
|
||||
|
||||
private const val GET_ACTIVITY_REQUEST_CODE = 0
|
||||
private const val CONNECT_REQUEST_CODE = 1
|
||||
private const val DISCONNECT_REQUEST_CODE = 2
|
||||
|
||||
class ServiceNotification(private val context: Context) {
|
||||
|
||||
private val upDownSymbols = when (Build.BRAND) {
|
||||
"Infinix" -> '˅' to '˄'
|
||||
else -> '↓' to '↑'
|
||||
}
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(context)
|
||||
|
||||
private val notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
|
||||
.setShowWhen(false)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
GET_ACTIVITY_REQUEST_CODE,
|
||||
Intent(context, AmneziaActivity::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
|
||||
private val zeroSpeed: String = with(TrafficData.ZERO) {
|
||||
formatSpeedString(rxString, txString)
|
||||
}
|
||||
|
||||
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
|
||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||
|
||||
Log.d(TAG, "Build notification: $serverName, $state")
|
||||
|
||||
return notificationBuilder
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setContentTitle(serverName ?: "AmneziaVPN")
|
||||
.setContentText(context.getString(state))
|
||||
.setSubText(speedString)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.clearActions()
|
||||
.apply {
|
||||
getAction(state)?.let {
|
||||
addAction(it)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildNotification(speed: TrafficData): Notification =
|
||||
notificationBuilder
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSubText(getSpeedString(speed))
|
||||
.build()
|
||||
|
||||
fun isNotificationEnabled(): Boolean {
|
||||
if (!context.isNotificationPermissionGranted()) return false
|
||||
if (!notificationManager.areNotificationsEnabled()) return false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateNotification(serverName: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateSpeed(speed: TrafficData) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(speed))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSpeedString(traffic: TrafficData) =
|
||||
if (traffic == TrafficData.ZERO) zeroSpeed
|
||||
else formatSpeedString(traffic.rxString, traffic.txString)
|
||||
|
||||
private fun formatSpeedString(rx: String, tx: String) = with(upDownSymbols) { "$first $rx $second $tx" }
|
||||
|
||||
private fun getAction(state: ProtocolState): Action? {
|
||||
return when (state) {
|
||||
CONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.disconnect),
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
DISCONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_DISCONNECT).apply {
|
||||
setPackage("org.amnezia.vpn")
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DISCONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.connect),
|
||||
createServicePendingIntent(
|
||||
context,
|
||||
CONNECT_REQUEST_CODE,
|
||||
Intent(context, AmneziaVpnService::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent::getForegroundService
|
||||
} else {
|
||||
PendingIntent::getService
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createNotificationChannel(context: Context) {
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
deleteNotificationChannel(OLD_NOTIFICATION_CHANNEL_ID)
|
||||
createNotificationChannel(
|
||||
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setShowBadge(false)
|
||||
.setSound(null, null)
|
||||
.setVibrationEnabled(false)
|
||||
.setLightsEnabled(false)
|
||||
.setName("AmneziaVPN")
|
||||
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isNotificationPermissionGranted(): Boolean =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
ContextCompat.checkSelfPermission(this, permission.POST_NOTIFICATIONS) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
Loading…
Add table
Add a link
Reference in a new issue