| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- package com.v2ray.ang.handler
- import android.app.Notification
- import android.app.NotificationChannel
- import android.app.NotificationManager
- import android.app.PendingIntent
- import android.app.Service
- import android.content.Context
- import android.content.Intent
- import android.graphics.Color
- import android.os.Build
- import androidx.annotation.RequiresApi
- import androidx.core.app.NotificationCompat
- import com.v2ray.ang.AppConfig
- import com.v2ray.ang.R
- import com.v2ray.ang.dto.ProfileItem
- import com.v2ray.ang.extension.toSpeedString
- import com.v2ray.ang.handler.V2RayServiceManager
- import com.v2ray.ang.ui.MainActivity
- import kotlinx.coroutines.CoroutineScope
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.Job
- import kotlinx.coroutines.delay
- import kotlinx.coroutines.isActive
- import kotlinx.coroutines.launch
- import kotlin.math.min
- object NotificationManager {
- private const val NOTIFICATION_ID = 1
- private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
- private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
- private const val NOTIFICATION_PENDING_INTENT_RESTART_V2RAY = 2
- private const val NOTIFICATION_ICON_THRESHOLD = 3000
- private var lastQueryTime = 0L
- private var mBuilder: NotificationCompat.Builder? = null
- private var speedNotificationJob: Job? = null
- private var mNotificationManager: NotificationManager? = null
- /**
- * Starts the speed notification.
- * @param currentConfig The current profile configuration.
- */
- fun startSpeedNotification(currentConfig: ProfileItem?) {
- if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) return
- if (speedNotificationJob != null || V2RayServiceManager.isRunning() == false) return
- lastQueryTime = System.currentTimeMillis()
- var lastZeroSpeed = false
- val outboundTags = currentConfig?.getAllOutboundTags()
- outboundTags?.remove(AppConfig.TAG_DIRECT)
- speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
- while (isActive) {
- val queryTime = System.currentTimeMillis()
- val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
- var proxyTotal = 0L
- val text = StringBuilder()
- outboundTags?.forEach {
- val up = V2RayServiceManager.queryStats(it, AppConfig.UPLINK)
- val down = V2RayServiceManager.queryStats(it, AppConfig.DOWNLINK)
- if (up + down > 0) {
- appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
- proxyTotal += up + down
- }
- }
- val directUplink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
- val directDownlink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK)
- val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
- if (!zeroSpeed || !lastZeroSpeed) {
- if (proxyTotal == 0L) {
- appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
- }
- appendSpeedString(
- text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
- directDownlink / sinceLastQueryInSeconds
- )
- updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
- }
- lastZeroSpeed = zeroSpeed
- lastQueryTime = queryTime
- delay(3000)
- }
- }
- }
- /**
- * Shows the notification.
- * @param currentConfig The current profile configuration.
- */
- fun showNotification(currentConfig: ProfileItem?) {
- val service = getService() ?: return
- val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- } else {
- PendingIntent.FLAG_UPDATE_CURRENT
- }
- val startMainIntent = Intent(service, MainActivity::class.java)
- val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
- val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
- stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
- stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
- val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
- val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
- restartV2RayIntent.`package` = AppConfig.ANG_PACKAGE
- restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
- val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)
- val channelId =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- createNotificationChannel()
- } else {
- // If earlier version channel ID is not used
- // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
- ""
- }
- mBuilder = NotificationCompat.Builder(service, channelId)
- .setSmallIcon(R.drawable.ic_stat_name)
- .setContentTitle(currentConfig?.remarks)
- .setPriority(NotificationCompat.PRIORITY_MIN)
- .setOngoing(true)
- .setShowWhen(false)
- .setOnlyAlertOnce(true)
- .setContentIntent(contentPendingIntent)
- .addAction(
- R.drawable.ic_delete_24dp,
- service.getString(R.string.notification_action_stop_v2ray),
- stopV2RayPendingIntent
- )
- .addAction(
- R.drawable.ic_delete_24dp,
- service.getString(R.string.title_service_restart),
- restartV2RayPendingIntent
- )
- //mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE)
- service.startForeground(NOTIFICATION_ID, mBuilder?.build())
- }
- /**
- * Cancels the notification.
- */
- fun cancelNotification() {
- val service = getService() ?: return
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- service.stopForeground(Service.STOP_FOREGROUND_REMOVE)
- } else {
- service.stopForeground(true)
- }
- mBuilder = null
- speedNotificationJob?.cancel()
- speedNotificationJob = null
- mNotificationManager = null
- }
- /**
- * Stops the speed notification.
- * @param currentConfig The current profile configuration.
- */
- fun stopSpeedNotification(currentConfig: ProfileItem?) {
- speedNotificationJob?.let {
- it.cancel()
- speedNotificationJob = null
- updateNotification(currentConfig?.remarks, 0, 0)
- }
- }
- /**
- * Creates a notification channel for Android O and above.
- * @return The channel ID.
- */
- @RequiresApi(Build.VERSION_CODES.O)
- private fun createNotificationChannel(): String {
- val channelId = AppConfig.RAY_NG_CHANNEL_ID
- val channelName = AppConfig.RAY_NG_CHANNEL_NAME
- val chan = NotificationChannel(
- channelId,
- channelName, NotificationManager.IMPORTANCE_HIGH
- )
- chan.lightColor = Color.DKGRAY
- chan.importance = NotificationManager.IMPORTANCE_NONE
- chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
- getNotificationManager()?.createNotificationChannel(chan)
- return channelId
- }
- /**
- * Updates the notification with the given content text and traffic data.
- * @param contentText The content text.
- * @param proxyTraffic The proxy traffic.
- * @param directTraffic The direct traffic.
- */
- private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
- if (mBuilder != null) {
- if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
- mBuilder?.setSmallIcon(R.drawable.ic_stat_name)
- } else if (proxyTraffic > directTraffic) {
- mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
- } else {
- mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
- }
- mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
- mBuilder?.setContentText(contentText)
- getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
- }
- }
- /**
- * Gets the notification manager.
- * @return The notification manager.
- */
- private fun getNotificationManager(): NotificationManager? {
- if (mNotificationManager == null) {
- val service = getService() ?: return null
- mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- }
- return mNotificationManager
- }
- /**
- * Appends the speed string to the given text.
- * @param text The text to append to.
- * @param name The name of the tag.
- * @param up The uplink speed.
- * @param down The downlink speed.
- */
- private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
- var n = name ?: "no tag"
- n = n.substring(0, min(n.length, 6))
- text.append(n)
- for (i in n.length..6 step 2) {
- text.append("\t")
- }
- text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
- }
- /**
- * Gets the service instance.
- * @return The service instance.
- */
- private fun getService(): Service? {
- return V2RayServiceManager.serviceControl?.get()?.getService()
- }
- }
|