14
14
15
15
package com.my.kizzy.feature_rpc_base.services
16
16
17
- import android.app.*
17
+ import android.app.Notification
18
+ import android.app.NotificationManager
19
+ import android.app.PendingIntent
20
+ import android.app.Service
18
21
import android.app.usage.UsageStats
19
22
import android.app.usage.UsageStatsManager
20
23
import android.content.Intent
@@ -28,15 +31,19 @@ import com.my.kizzy.feature_rpc_base.setLargeIcon
28
31
import com.my.kizzy.preference.Prefs
29
32
import com.my.kizzy.resources.R
30
33
import dagger.hilt.android.AndroidEntryPoint
31
- import kotlinx.coroutines.*
34
+ import kotlinx.coroutines.CoroutineScope
35
+ import kotlinx.coroutines.cancel
36
+ import kotlinx.coroutines.delay
37
+ import kotlinx.coroutines.isActive
38
+ import kotlinx.coroutines.launch
32
39
import kotlinx.serialization.decodeFromString
33
40
import kotlinx.serialization.json.Json
34
- import java.util.*
41
+ import java.util.SortedMap
42
+ import java.util.TreeMap
35
43
import javax.inject.Inject
36
44
37
45
@AndroidEntryPoint
38
46
class AppDetectionService : Service () {
39
- private var notifset = false
40
47
41
48
@Inject
42
49
lateinit var kizzyRPC: KizzyRPC
@@ -49,94 +56,16 @@ class AppDetectionService : Service() {
49
56
50
57
@Inject
51
58
lateinit var notificationManager: NotificationManager
52
- override fun onBind (intent : Intent ): IBinder ? {
53
- return null
54
- }
59
+
60
+ private lateinit var pendingIntent: PendingIntent
61
+
62
+ override fun onBind (intent : Intent ): IBinder ? = null
55
63
56
64
override fun onStartCommand (intent : Intent ? , flags : Int , startId : Int ): Int {
57
- if (intent?.action.equals(Constants .ACTION_STOP_SERVICE )) stopSelf()
58
- else {
59
- notifset = false
60
- val apps = Prefs [Prefs .ENABLED_APPS , " []" ]
61
- val enabledPackages: List <String > = Json .decodeFromString(apps)
62
-
63
- val stopIntent = Intent (this , AppDetectionService ::class .java)
64
- stopIntent.action = Constants .ACTION_STOP_SERVICE
65
- val pendingIntent: PendingIntent = PendingIntent .getService(
66
- this ,
67
- 0 , stopIntent, PendingIntent .FLAG_IMMUTABLE
68
- )
69
- val rpcButtonsString = Prefs [Prefs .RPC_BUTTONS_DATA , " {}" ]
70
- val rpcButtons = Json .decodeFromString<RpcButtons >(rpcButtonsString)
71
- scope.launch {
72
- while (isActive) {
73
- val usageStatsManager =
74
- (this @AppDetectionService).getSystemService(USAGE_STATS_SERVICE ) as UsageStatsManager
75
- val currentTimeMillis = System .currentTimeMillis()
76
- val queryUsageStats = usageStatsManager.queryUsageStats(
77
- UsageStatsManager .INTERVAL_DAILY ,
78
- currentTimeMillis - 10000 ,
79
- currentTimeMillis
80
- )
81
- if (queryUsageStats != null && queryUsageStats.size > 1 ) {
82
- val treeMap: SortedMap <Long , UsageStats > = TreeMap ()
83
- for (usageStats in queryUsageStats) {
84
- treeMap[usageStats.lastTimeUsed] = usageStats
85
- }
86
- if (! (treeMap.isEmpty() ||
87
- treeMap[treeMap.lastKey()]?.packageName == " com.my.kizzy" ||
88
- treeMap[treeMap.lastKey()]?.packageName == " com.discord" )
89
- ) {
90
- val packageName = treeMap[treeMap.lastKey()]!! .packageName
91
- Objects .requireNonNull(packageName)
92
- if (enabledPackages.contains(packageName)) {
93
- if (! kizzyRPC.isRpcRunning()) {
94
- kizzyRPC.apply {
95
- setName(AppUtils .getAppName(packageName))
96
- setStartTimestamps(System .currentTimeMillis())
97
- setStatus(" dnd" )
98
- setLargeImage(RpcImage .ApplicationIcon (packageName, this @AppDetectionService))
99
- if (Prefs [Prefs .USE_RPC_BUTTONS , false ]) {
100
- with (rpcButtons) {
101
- setButton1(button1.takeIf { it.isNotEmpty() })
102
- setButton1URL(button1Url.takeIf { it.isNotEmpty() })
103
- setButton2(button2.takeIf { it.isNotEmpty() })
104
- setButton2URL(button2Url.takeIf { it.isNotEmpty() })
105
- }
106
- }
107
- build()
108
- }
109
- }
110
- notificationManager.notify(
111
- Constants .NOTIFICATION_ID , notificationBuilder
112
- .setContentText(packageName)
113
- .setLargeIcon(
114
- rpcImage = RpcImage .ApplicationIcon (packageName, this @AppDetectionService),
115
- context = this @AppDetectionService
116
- )
117
- .build()
118
- )
119
- notifset = true
120
- } else {
121
- if (kizzyRPC.isRpcRunning()) {
122
- kizzyRPC.closeRPC()
123
- }
124
- notifset = false
125
- }
126
- }
127
- }
128
- if (! notifset) {
129
- startForeground(
130
- Constants .NOTIFICATION_ID , notificationBuilder
131
- .setSmallIcon(R .drawable.ic_apps)
132
- .setContentTitle(" Service enabled" )
133
- .addAction(R .drawable.ic_apps, " Exit" , pendingIntent)
134
- .build()
135
- )
136
- }
137
- delay(5000 )
138
- }
139
- }
65
+ if (intent?.action == Constants .ACTION_STOP_SERVICE ) {
66
+ stopSelf()
67
+ } else {
68
+ handleAppDetection()
140
69
}
141
70
return super .onStartCommand(intent, flags, startId)
142
71
}
@@ -146,4 +75,132 @@ class AppDetectionService : Service() {
146
75
kizzyRPC.closeRPC()
147
76
super .onDestroy()
148
77
}
149
- }
78
+
79
+ private fun handleAppDetection () {
80
+ val enabledPackages = getEnabledPackages()
81
+
82
+ val stopIntent = createStopIntent()
83
+ pendingIntent = createPendingIntent(stopIntent)
84
+ // Adding action to notification builder here to avoid having multiple Exit buttons
85
+ // https://github.com/dead8309/Kizzy/issues/197
86
+ notificationBuilder
87
+ .setSmallIcon(R .drawable.ic_apps)
88
+ .addAction(R .drawable.ic_apps, " Exit" , pendingIntent)
89
+
90
+ startForeground(Constants .NOTIFICATION_ID , createDefaultNotification())
91
+
92
+ val rpcButtons = getRpcButtons()
93
+
94
+ scope.launch {
95
+ while (isActive) {
96
+ val queryUsageStats = getUsageStats()
97
+
98
+ if (queryUsageStats != null && queryUsageStats.size > 1 ) {
99
+ val packageName = getLatestPackageName(queryUsageStats)
100
+ if (packageName != null && packageName !in EXCLUDED_APPS ) {
101
+ handleValidPackage(packageName, enabledPackages, rpcButtons)
102
+ }
103
+ }
104
+ delay(5000 )
105
+ }
106
+ }
107
+ }
108
+
109
+ private fun getEnabledPackages (): List <String > {
110
+ val apps = Prefs [Prefs .ENABLED_APPS , " []" ]
111
+ return Json .decodeFromString(apps)
112
+ }
113
+
114
+ private fun getRpcButtons (): RpcButtons {
115
+ val rpcButtonsString = Prefs [Prefs .RPC_BUTTONS_DATA , " {}" ]
116
+ return Json .decodeFromString(rpcButtonsString)
117
+ }
118
+
119
+ private fun getUsageStats (): List <UsageStats >? {
120
+ val usageStatsManager = getSystemService(USAGE_STATS_SERVICE ) as UsageStatsManager
121
+ val currentTimeMillis = System .currentTimeMillis()
122
+ return usageStatsManager.queryUsageStats(
123
+ UsageStatsManager .INTERVAL_DAILY ,
124
+ currentTimeMillis - 10000 ,
125
+ currentTimeMillis
126
+ )
127
+ }
128
+
129
+ private fun getLatestPackageName (usageStats : List <UsageStats >): String? {
130
+ val treeMap: SortedMap <Long , UsageStats > = TreeMap ()
131
+ for (usageStatsItem in usageStats) {
132
+ treeMap[usageStatsItem.lastTimeUsed] = usageStatsItem
133
+ }
134
+ return treeMap.lastKey()?.let { treeMap[it]?.packageName }
135
+ }
136
+
137
+ private suspend fun handleValidPackage (
138
+ packageName : String ,
139
+ enabledPackages : List <String >,
140
+ rpcButtons : RpcButtons
141
+ ) {
142
+ if (packageName in enabledPackages) {
143
+ handleEnabledPackage(packageName, rpcButtons)
144
+ } else {
145
+ handleDisabledPackage()
146
+ }
147
+ }
148
+
149
+ private suspend fun handleEnabledPackage (packageName : String , rpcButtons : RpcButtons ) {
150
+ if (! kizzyRPC.isRpcRunning()) {
151
+ kizzyRPC.apply {
152
+ setName(AppUtils .getAppName(packageName))
153
+ setStartTimestamps(System .currentTimeMillis())
154
+ setStatus(" dnd" )
155
+ setLargeImage(RpcImage .ApplicationIcon (packageName, this @AppDetectionService))
156
+ if (Prefs [Prefs .USE_RPC_BUTTONS , false ]) {
157
+ with (rpcButtons) {
158
+ setButton1(button1.takeIf { it.isNotEmpty() })
159
+ setButton1URL(button1Url.takeIf { it.isNotEmpty() })
160
+ setButton2(button2.takeIf { it.isNotEmpty() })
161
+ setButton2URL(button2Url.takeIf { it.isNotEmpty() })
162
+ }
163
+ }
164
+ build()
165
+ }
166
+ }
167
+ notificationManager.notify(
168
+ Constants .NOTIFICATION_ID , notificationBuilder
169
+ .setContentText(packageName)
170
+ .setLargeIcon(
171
+ rpcImage = RpcImage .ApplicationIcon (packageName, this @AppDetectionService),
172
+ context = this @AppDetectionService
173
+ )
174
+ .build()
175
+ )
176
+ }
177
+
178
+ private fun handleDisabledPackage () {
179
+ if (kizzyRPC.isRpcRunning()) {
180
+ kizzyRPC.closeRPC()
181
+ }
182
+ notificationManager.notify(Constants .NOTIFICATION_ID , createDefaultNotification())
183
+ }
184
+
185
+ private fun createDefaultNotification (): Notification {
186
+ return Notification .Builder (this ,Constants .CHANNEL_ID )
187
+ .setSmallIcon(R .drawable.ic_apps)
188
+ .setContentTitle(" Service enabled" )
189
+ .addAction(R .drawable.ic_apps, " Exit" , pendingIntent)
190
+ .build()
191
+ }
192
+
193
+ private fun createStopIntent (): Intent {
194
+ val stopIntent = Intent (this , AppDetectionService ::class .java)
195
+ stopIntent.action = Constants .ACTION_STOP_SERVICE
196
+ return stopIntent
197
+ }
198
+
199
+ private fun createPendingIntent (stopIntent : Intent ): PendingIntent {
200
+ return PendingIntent .getService(this , 0 , stopIntent, PendingIntent .FLAG_IMMUTABLE )
201
+ }
202
+
203
+ companion object {
204
+ val EXCLUDED_APPS = listOf (" com.my.kizzy" , " com.discord" )
205
+ }
206
+ }
0 commit comments