Android notification and routing (#797)

Android notification and routing
This commit is contained in:
albexk 2024-05-12 18:04:14 +03:00 committed by GitHub
parent ff348a348c
commit abb3c918e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1108 additions and 543 deletions

View 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