Skip to content

Commit 17baa6c

Browse files
committed
fix: #197
- register exit action to notificationBuilder only one time - made code a slight more readable
1 parent eb289bf commit 17baa6c

File tree

1 file changed

+148
-91
lines changed

1 file changed

+148
-91
lines changed

feature_rpc_base/src/main/java/com/my/kizzy/feature_rpc_base/services/AppDetectionService.kt

Lines changed: 148 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
package com.my.kizzy.feature_rpc_base.services
1616

17-
import android.app.*
17+
import android.app.Notification
18+
import android.app.NotificationManager
19+
import android.app.PendingIntent
20+
import android.app.Service
1821
import android.app.usage.UsageStats
1922
import android.app.usage.UsageStatsManager
2023
import android.content.Intent
@@ -28,15 +31,19 @@ import com.my.kizzy.feature_rpc_base.setLargeIcon
2831
import com.my.kizzy.preference.Prefs
2932
import com.my.kizzy.resources.R
3033
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
3239
import kotlinx.serialization.decodeFromString
3340
import kotlinx.serialization.json.Json
34-
import java.util.*
41+
import java.util.SortedMap
42+
import java.util.TreeMap
3543
import javax.inject.Inject
3644

3745
@AndroidEntryPoint
3846
class AppDetectionService : Service() {
39-
private var notifset = false
4047

4148
@Inject
4249
lateinit var kizzyRPC: KizzyRPC
@@ -49,94 +56,16 @@ class AppDetectionService : Service() {
4956

5057
@Inject
5158
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
5563

5664
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()
14069
}
14170
return super.onStartCommand(intent, flags, startId)
14271
}
@@ -146,4 +75,132 @@ class AppDetectionService : Service() {
14675
kizzyRPC.closeRPC()
14776
super.onDestroy()
14877
}
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

Comments
 (0)