NotificationManager.kt 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. package com.v2ray.ang.handler
  2. import android.app.Notification
  3. import android.app.NotificationChannel
  4. import android.app.NotificationManager
  5. import android.app.PendingIntent
  6. import android.app.Service
  7. import android.content.Context
  8. import android.content.Intent
  9. import android.graphics.Color
  10. import android.os.Build
  11. import androidx.annotation.RequiresApi
  12. import androidx.core.app.NotificationCompat
  13. import com.v2ray.ang.AppConfig
  14. import com.v2ray.ang.R
  15. import com.v2ray.ang.dto.ProfileItem
  16. import com.v2ray.ang.extension.toSpeedString
  17. import com.v2ray.ang.handler.V2RayServiceManager
  18. import com.v2ray.ang.ui.MainActivity
  19. import kotlinx.coroutines.CoroutineScope
  20. import kotlinx.coroutines.Dispatchers
  21. import kotlinx.coroutines.Job
  22. import kotlinx.coroutines.delay
  23. import kotlinx.coroutines.isActive
  24. import kotlinx.coroutines.launch
  25. import kotlin.math.min
  26. object NotificationManager {
  27. private const val NOTIFICATION_ID = 1
  28. private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
  29. private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
  30. private const val NOTIFICATION_PENDING_INTENT_RESTART_V2RAY = 2
  31. private const val NOTIFICATION_ICON_THRESHOLD = 3000
  32. private var lastQueryTime = 0L
  33. private var mBuilder: NotificationCompat.Builder? = null
  34. private var speedNotificationJob: Job? = null
  35. private var mNotificationManager: NotificationManager? = null
  36. /**
  37. * Starts the speed notification.
  38. * @param currentConfig The current profile configuration.
  39. */
  40. fun startSpeedNotification(currentConfig: ProfileItem?) {
  41. if (MmkvManager.decodeSettingsBool(AppConfig.PREF_SPEED_ENABLED) != true) return
  42. if (speedNotificationJob != null || V2RayServiceManager.isRunning() == false) return
  43. lastQueryTime = System.currentTimeMillis()
  44. var lastZeroSpeed = false
  45. val outboundTags = currentConfig?.getAllOutboundTags()
  46. outboundTags?.remove(AppConfig.TAG_DIRECT)
  47. speedNotificationJob = CoroutineScope(Dispatchers.IO).launch {
  48. while (isActive) {
  49. val queryTime = System.currentTimeMillis()
  50. val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
  51. var proxyTotal = 0L
  52. val text = StringBuilder()
  53. outboundTags?.forEach {
  54. val up = V2RayServiceManager.queryStats(it, AppConfig.UPLINK)
  55. val down = V2RayServiceManager.queryStats(it, AppConfig.DOWNLINK)
  56. if (up + down > 0) {
  57. appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
  58. proxyTotal += up + down
  59. }
  60. }
  61. val directUplink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.UPLINK)
  62. val directDownlink = V2RayServiceManager.queryStats(AppConfig.TAG_DIRECT, AppConfig.DOWNLINK)
  63. val zeroSpeed = proxyTotal == 0L && directUplink == 0L && directDownlink == 0L
  64. if (!zeroSpeed || !lastZeroSpeed) {
  65. if (proxyTotal == 0L) {
  66. appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
  67. }
  68. appendSpeedString(
  69. text, AppConfig.TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
  70. directDownlink / sinceLastQueryInSeconds
  71. )
  72. updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
  73. }
  74. lastZeroSpeed = zeroSpeed
  75. lastQueryTime = queryTime
  76. delay(3000)
  77. }
  78. }
  79. }
  80. /**
  81. * Shows the notification.
  82. * @param currentConfig The current profile configuration.
  83. */
  84. fun showNotification(currentConfig: ProfileItem?) {
  85. val service = getService() ?: return
  86. val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  87. PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
  88. } else {
  89. PendingIntent.FLAG_UPDATE_CURRENT
  90. }
  91. val startMainIntent = Intent(service, MainActivity::class.java)
  92. val contentPendingIntent = PendingIntent.getActivity(service, NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent, flags)
  93. val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
  94. stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
  95. stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
  96. val stopV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent, flags)
  97. val restartV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
  98. restartV2RayIntent.`package` = AppConfig.ANG_PACKAGE
  99. restartV2RayIntent.putExtra("key", AppConfig.MSG_STATE_RESTART)
  100. val restartV2RayPendingIntent = PendingIntent.getBroadcast(service, NOTIFICATION_PENDING_INTENT_RESTART_V2RAY, restartV2RayIntent, flags)
  101. val channelId =
  102. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  103. createNotificationChannel()
  104. } else {
  105. // If earlier version channel ID is not used
  106. // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
  107. ""
  108. }
  109. mBuilder = NotificationCompat.Builder(service, channelId)
  110. .setSmallIcon(R.drawable.ic_stat_name)
  111. .setContentTitle(currentConfig?.remarks)
  112. .setPriority(NotificationCompat.PRIORITY_MIN)
  113. .setOngoing(true)
  114. .setShowWhen(false)
  115. .setOnlyAlertOnce(true)
  116. .setContentIntent(contentPendingIntent)
  117. .addAction(
  118. R.drawable.ic_delete_24dp,
  119. service.getString(R.string.notification_action_stop_v2ray),
  120. stopV2RayPendingIntent
  121. )
  122. .addAction(
  123. R.drawable.ic_delete_24dp,
  124. service.getString(R.string.title_service_restart),
  125. restartV2RayPendingIntent
  126. )
  127. //mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE)
  128. service.startForeground(NOTIFICATION_ID, mBuilder?.build())
  129. }
  130. /**
  131. * Cancels the notification.
  132. */
  133. fun cancelNotification() {
  134. val service = getService() ?: return
  135. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  136. service.stopForeground(Service.STOP_FOREGROUND_REMOVE)
  137. } else {
  138. service.stopForeground(true)
  139. }
  140. mBuilder = null
  141. speedNotificationJob?.cancel()
  142. speedNotificationJob = null
  143. mNotificationManager = null
  144. }
  145. /**
  146. * Stops the speed notification.
  147. * @param currentConfig The current profile configuration.
  148. */
  149. fun stopSpeedNotification(currentConfig: ProfileItem?) {
  150. speedNotificationJob?.let {
  151. it.cancel()
  152. speedNotificationJob = null
  153. updateNotification(currentConfig?.remarks, 0, 0)
  154. }
  155. }
  156. /**
  157. * Creates a notification channel for Android O and above.
  158. * @return The channel ID.
  159. */
  160. @RequiresApi(Build.VERSION_CODES.O)
  161. private fun createNotificationChannel(): String {
  162. val channelId = AppConfig.RAY_NG_CHANNEL_ID
  163. val channelName = AppConfig.RAY_NG_CHANNEL_NAME
  164. val chan = NotificationChannel(
  165. channelId,
  166. channelName, NotificationManager.IMPORTANCE_HIGH
  167. )
  168. chan.lightColor = Color.DKGRAY
  169. chan.importance = NotificationManager.IMPORTANCE_NONE
  170. chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
  171. getNotificationManager()?.createNotificationChannel(chan)
  172. return channelId
  173. }
  174. /**
  175. * Updates the notification with the given content text and traffic data.
  176. * @param contentText The content text.
  177. * @param proxyTraffic The proxy traffic.
  178. * @param directTraffic The direct traffic.
  179. */
  180. private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
  181. if (mBuilder != null) {
  182. if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
  183. mBuilder?.setSmallIcon(R.drawable.ic_stat_name)
  184. } else if (proxyTraffic > directTraffic) {
  185. mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
  186. } else {
  187. mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
  188. }
  189. mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
  190. mBuilder?.setContentText(contentText)
  191. getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
  192. }
  193. }
  194. /**
  195. * Gets the notification manager.
  196. * @return The notification manager.
  197. */
  198. private fun getNotificationManager(): NotificationManager? {
  199. if (mNotificationManager == null) {
  200. val service = getService() ?: return null
  201. mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
  202. }
  203. return mNotificationManager
  204. }
  205. /**
  206. * Appends the speed string to the given text.
  207. * @param text The text to append to.
  208. * @param name The name of the tag.
  209. * @param up The uplink speed.
  210. * @param down The downlink speed.
  211. */
  212. private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
  213. var n = name ?: "no tag"
  214. n = n.substring(0, min(n.length, 6))
  215. text.append(n)
  216. for (i in n.length..6 step 2) {
  217. text.append("\t")
  218. }
  219. text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
  220. }
  221. /**
  222. * Gets the service instance.
  223. * @return The service instance.
  224. */
  225. private fun getService(): Service? {
  226. return V2RayServiceManager.serviceControl?.get()?.getService()
  227. }
  228. }