Skip to content

Commit d971af8

Browse files
committed
fix: Split window handling in usage stats
- Refine ShizukuActivityManager event handling and logging - Improve UsageLockService foreground app detection and monitoring logic
1 parent 85dba16 commit d971af8

11 files changed

Lines changed: 84 additions & 79 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
5959
android:excludeFromRecents="true"
6060
android:exported="false"
61+
android:launchMode="singleInstance"
62+
android:noHistory="true"
6163
android:taskAffinity=""
6264
android:theme="@android:style/Theme.Material.NoActionBar.TranslucentDecor" />
6365

@@ -73,7 +75,7 @@
7375
android:foregroundServiceType="specialUse|systemExempted" />
7476

7577
<service
76-
android:name=".services.ExperimentalAppLockService"
78+
android:name=".services.UsageLockService"
7779
android:enabled="true"
7880
android:exported="false"
7981
android:foregroundServiceType="specialUse|systemExempted">

app/src/main/java/dev/pranav/applock/core/broadcast/AppLockServiceStarter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import android.util.Log
77
import androidx.core.content.ContextCompat
88
import dev.pranav.applock.data.repository.BackendImplementation
99
import dev.pranav.applock.services.AppLockAccessibilityService
10-
import dev.pranav.applock.services.ExperimentalAppLockService
1110
import dev.pranav.applock.services.ShizukuAppLockService
11+
import dev.pranav.applock.services.UsageLockService
1212
import dev.pranav.applock.services.isServiceRunning
1313

1414
object AppLockServiceStarter {
@@ -32,7 +32,7 @@ object AppLockServiceStarter {
3232
}
3333

3434
BackendImplementation.USAGE_STATS -> {
35-
startService(context, ExperimentalAppLockService::class.java)
35+
startService(context, UsageLockService::class.java)
3636
}
3737
}
3838
}

app/src/main/java/dev/pranav/applock/core/broadcast/BootReceiver.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import dev.pranav.applock.core.utils.LogUtils
1010
import dev.pranav.applock.core.utils.appLockRepository
1111
import dev.pranav.applock.data.repository.BackendImplementation
1212
import dev.pranav.applock.services.AppLockAccessibilityService
13-
import dev.pranav.applock.services.ExperimentalAppLockService
1413
import dev.pranav.applock.services.ShizukuAppLockService
14+
import dev.pranav.applock.services.UsageLockService
1515
import dev.pranav.applock.services.isServiceRunning
1616

1717
class BootReceiver : BroadcastReceiver() {
@@ -65,7 +65,7 @@ class BootReceiver : BroadcastReceiver() {
6565
}
6666

6767
BackendImplementation.USAGE_STATS -> {
68-
startService(context, ExperimentalAppLockService::class.java)
68+
startService(context, UsageLockService::class.java)
6969
}
7070
}
7171
}

app/src/main/java/dev/pranav/applock/data/manager/BackendServiceManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package dev.pranav.applock.data.manager
33
import android.util.Log
44
import dev.pranav.applock.data.repository.BackendImplementation
55
import dev.pranav.applock.services.AppLockAccessibilityService
6-
import dev.pranav.applock.services.ExperimentalAppLockService
76
import dev.pranav.applock.services.ShizukuAppLockService
7+
import dev.pranav.applock.services.UsageLockService
88

99
/**
1010
* Manages backend service operations and switching between different implementations.
@@ -44,7 +44,7 @@ class BackendServiceManager {
4444
private fun getBackendForService(serviceClass: Class<*>): BackendImplementation? {
4545
return when (serviceClass) {
4646
AppLockAccessibilityService::class.java -> BackendImplementation.ACCESSIBILITY
47-
ExperimentalAppLockService::class.java -> BackendImplementation.USAGE_STATS
47+
UsageLockService::class.java -> BackendImplementation.USAGE_STATS
4848
ShizukuAppLockService::class.java -> BackendImplementation.SHIZUKU
4949
else -> null
5050
}

app/src/main/java/dev/pranav/applock/features/appintro/ui/AppIntroScreen.kt

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,13 @@ import androidx.activity.result.contract.ActivityResultContracts
1414
import androidx.compose.foundation.BorderStroke
1515
import androidx.compose.foundation.background
1616
import androidx.compose.foundation.clickable
17-
import androidx.compose.foundation.layout.Arrangement
18-
import androidx.compose.foundation.layout.Column
19-
import androidx.compose.foundation.layout.Row
20-
import androidx.compose.foundation.layout.Spacer
21-
import androidx.compose.foundation.layout.fillMaxSize
22-
import androidx.compose.foundation.layout.fillMaxWidth
23-
import androidx.compose.foundation.layout.height
24-
import androidx.compose.foundation.layout.padding
25-
import androidx.compose.foundation.layout.size
26-
import androidx.compose.foundation.layout.width
17+
import androidx.compose.foundation.layout.*
2718
import androidx.compose.material.icons.Icons
2819
import androidx.compose.material.icons.filled.Lock
2920
import androidx.compose.material.icons.filled.Notifications
3021
import androidx.compose.material.icons.filled.QueryStats
31-
import androidx.compose.material3.Card
32-
import androidx.compose.material3.CardDefaults
33-
import androidx.compose.material3.Icon
34-
import androidx.compose.material3.RadioButton
35-
import androidx.compose.material3.RadioButtonDefaults
36-
import androidx.compose.material3.Text
37-
import androidx.compose.runtime.Composable
38-
import androidx.compose.runtime.LaunchedEffect
39-
import androidx.compose.runtime.getValue
40-
import androidx.compose.runtime.mutableStateOf
41-
import androidx.compose.runtime.remember
42-
import androidx.compose.runtime.setValue
22+
import androidx.compose.material3.*
23+
import androidx.compose.runtime.*
4324
import androidx.compose.ui.Alignment
4425
import androidx.compose.ui.Modifier
4526
import androidx.compose.ui.graphics.Color
@@ -65,8 +46,8 @@ import dev.pranav.applock.core.utils.isAccessibilityServiceEnabled
6546
import dev.pranav.applock.core.utils.launchBatterySettings
6647
import dev.pranav.applock.data.repository.BackendImplementation
6748
import dev.pranav.applock.features.appintro.domain.AppIntroManager
68-
import dev.pranav.applock.services.ExperimentalAppLockService
6949
import dev.pranav.applock.services.ShizukuAppLockService
50+
import dev.pranav.applock.services.UsageLockService
7051
import dev.pranav.applock.ui.icons.Accessibility
7152
import dev.pranav.applock.ui.icons.BatterySaver
7253
import dev.pranav.applock.ui.icons.Display
@@ -397,7 +378,7 @@ fun AppIntroScreen(navController: NavController) {
397378
context.appLockRepository()
398379
.setBackendImplementation(BackendImplementation.USAGE_STATS)
399380
context.startService(
400-
Intent(context, ExperimentalAppLockService::class.java)
381+
Intent(context, UsageLockService::class.java)
401382
)
402383
true
403384
}

app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import kotlinx.coroutines.Dispatchers
5757
import kotlinx.coroutines.launch
5858
import java.util.concurrent.Executor
5959

60-
class PasswordOverlayActivity : FragmentActivity() {
60+
class PasswordOverlayActivity: FragmentActivity() {
6161
private lateinit var executor: Executor
6262
private lateinit var biometricPrompt: BiometricPrompt
6363
private lateinit var promptInfo: BiometricPrompt.PromptInfo
@@ -121,11 +121,13 @@ class PasswordOverlayActivity : FragmentActivity() {
121121
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
122122
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
123123
WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON or
124-
WindowManager.LayoutParams.FLAG_SECURE
124+
WindowManager.LayoutParams.FLAG_SECURE or
125+
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
126+
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
125127
)
126128

127129
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
128-
setShowWhenLocked(true)
130+
//setShowWhenLocked(true)
129131
setTurnScreenOn(true)
130132
}
131133

@@ -136,6 +138,8 @@ class PasswordOverlayActivity : FragmentActivity() {
136138

137139
val layoutParams = window.attributes
138140
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
141+
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT
142+
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
139143

140144
if (appLockRepository.shouldUseMaxBrightness()) {
141145
layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
@@ -211,6 +215,8 @@ class PasswordOverlayActivity : FragmentActivity() {
211215
triggeringPackageName = triggeringPackageNameFromIntent,
212216
onPinAttempt = onPinAttemptCallback
213217
)
218+
219+
BackHandler { }
214220
}
215221
}
216222
}
@@ -237,7 +243,7 @@ class PasswordOverlayActivity : FragmentActivity() {
237243
}
238244

239245
private val authenticationCallbackInternal =
240-
object : BiometricPrompt.AuthenticationCallback() {
246+
object: BiometricPrompt.AuthenticationCallback() {
241247
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
242248
super.onAuthenticationError(errorCode, errString)
243249
isBiometricPromptShowingLocal = false
@@ -292,7 +298,7 @@ class PasswordOverlayActivity : FragmentActivity() {
292298

293299
override fun onPause() {
294300
super.onPause()
295-
if (!isChangingConfigurations() && !isBiometricPromptShowingLocal) {
301+
if (!isChangingConfigurations && !isBiometricPromptShowingLocal) {
296302
Log.d(TAG, "Overlay paused; lock screen hidden but app remains locked")
297303
AppLockManager.isLockScreenShown.set(false)
298304
}
@@ -305,7 +311,7 @@ class PasswordOverlayActivity : FragmentActivity() {
305311

306312
override fun onStop() {
307313
super.onStop()
308-
if (isChangingConfigurations()) {
314+
if (isChangingConfigurations) {
309315
return
310316
}
311317
Log.d(TAG, "Overlay stopped; finishing lock overlay")
@@ -497,8 +503,6 @@ fun PasswordOverlayScreen(
497503
}
498504
}
499505
}
500-
501-
BackHandler {}
502506
}
503507

504508
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalAnimationApi::class)
@@ -557,7 +561,7 @@ fun PasswordIndicators(
557561
) {
558562
LazyRow(
559563
state = lazyListState,
560-
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp),
564+
contentPadding = PaddingValues(horizontal = 16.dp),
561565
horizontalArrangement = Arrangement.spacedBy(
562566
indicatorSpacing,
563567
Alignment.CenterHorizontally

app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ import dev.pranav.applock.core.utils.openAccessibilitySettings
4646
import dev.pranav.applock.data.repository.AppLockRepository
4747
import dev.pranav.applock.data.repository.BackendImplementation
4848
import dev.pranav.applock.features.admin.AdminDisableActivity
49-
import dev.pranav.applock.services.ExperimentalAppLockService
5049
import dev.pranav.applock.services.ShizukuAppLockService
50+
import dev.pranav.applock.services.UsageLockService
5151
import dev.pranav.applock.ui.components.DonateButton
5252
import dev.pranav.applock.ui.icons.*
5353
import rikka.shizuku.Shizuku
@@ -811,7 +811,7 @@ fun BackendSelectionCard(
811811
selectedBackend = backend
812812
appLockRepository.setBackendImplementation(BackendImplementation.USAGE_STATS)
813813
context.startService(
814-
Intent(context, ExperimentalAppLockService::class.java)
814+
Intent(context, UsageLockService::class.java)
815815
)
816816
}
817817
BackendImplementation.ACCESSIBILITY -> {

app/src/main/java/dev/pranav/applock/services/AppLockManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ object AppLockManager {
8888

8989
private val ALL_APP_LOCK_SERVICES = setOf(
9090
ShizukuAppLockService::class.java,
91-
ExperimentalAppLockService::class.java
91+
UsageLockService::class.java
9292
)
9393

9494
fun unlockApp(packageName: String) {

app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt renamed to app/src/main/java/dev/pranav/applock/services/UsageLockService.kt

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ import dev.pranav.applock.features.lockscreen.ui.PasswordOverlayActivity
3030
import java.util.Timer
3131
import kotlin.concurrent.timerTask
3232

33-
class ExperimentalAppLockService : Service() {
34-
private val TAG = "ExperimentalAppLockService"
33+
class UsageLockService: Service() {
34+
private val TAG = "UsageLockService"
3535
private val NOTIFICATION_ID = 113
36-
private val CHANNEL_ID = "ExperimentalAppLockServiceChannel"
36+
private val CHANNEL_ID = "UsageLockServiceChannel"
3737

3838
companion object {
3939
@Volatile
@@ -47,6 +47,7 @@ class ExperimentalAppLockService : Service() {
4747

4848
private var timer: Timer? = null
4949
private var previousForegroundPackage = ""
50+
private var pauseMonitoring = false
5051

5152
private val screenStateReceiver = object: android.content.BroadcastReceiver() {
5253
override fun onReceive(context: android.content.Context?, intent: Intent?) {
@@ -58,6 +59,9 @@ class ExperimentalAppLockService : Service() {
5859
AppLockManager.isLockScreenShown.set(false)
5960
AppLockManager.clearTemporarilyUnlockedApp()
6061
previousForegroundPackage = ""
62+
pauseMonitoring = true
63+
} else if (intent?.action == Intent.ACTION_USER_PRESENT) {
64+
pauseMonitoring = false
6165
}
6266
}
6367
}
@@ -93,7 +97,7 @@ class ExperimentalAppLockService : Service() {
9397

9498
try {
9599
unregisterReceiver(screenStateReceiver)
96-
} catch (e: IllegalArgumentException) {
100+
} catch (_: IllegalArgumentException) {
97101
Log.w(TAG, "Receiver not registered or already unregistered")
98102
}
99103

@@ -106,12 +110,8 @@ class ExperimentalAppLockService : Service() {
106110
super.onTaskRemoved(rootIntent)
107111
if (shouldStartService(appLockRepository, this::class.java)) {
108112
try {
109-
val startIntent = Intent(this, ExperimentalAppLockService::class.java)
110-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
111-
ContextCompat.startForegroundService(this, startIntent)
112-
} else {
113-
startService(startIntent)
114-
}
113+
val startIntent = Intent(this, UsageLockService::class.java)
114+
ContextCompat.startForegroundService(this, startIntent)
115115
Log.d(TAG, "Re-started ExperimentalAppLockService after task removal")
116116
} catch (e: Exception) {
117117
Log.e(TAG, "Failed to restart service after task removal", e)
@@ -121,8 +121,6 @@ class ExperimentalAppLockService : Service() {
121121

122122
override fun onBind(intent: Intent?): IBinder? = null
123123

124-
// --- Monitoring ---
125-
126124
private fun startMonitoringTimer() {
127125
timer?.cancel()
128126
timer = Timer("AppLockUsageStatsMonitor", true)
@@ -146,13 +144,23 @@ class ExperimentalAppLockService : Service() {
146144
val triggeringPackage = previousForegroundPackage
147145
previousForegroundPackage = currentPackage
148146

147+
Log.d(
148+
"Usage",
149+
"cur: $currentPackage, prev: $triggeringPackage, unlocked ${
150+
AppLockManager.isAppTemporarilyUnlocked(currentPackage)
151+
}"
152+
)
153+
149154
if (isExclusionApp(currentPackage)) return
150155

151156
if (triggeringPackage in appLockRepository.getTriggerExcludedApps()) {
152157
return
153158
}
154159

155-
if (currentPackage == triggeringPackage) return
160+
if (currentPackage == triggeringPackage && AppLockManager.isAppTemporarilyUnlocked(
161+
currentPackage
162+
)
163+
) return
156164

157165
checkAndLockApp(currentPackage, triggeringPackage, System.currentTimeMillis())
158166
} catch (e: Exception) {
@@ -176,21 +184,42 @@ class ExperimentalAppLockService : Service() {
176184
*/
177185
private fun getCurrentForegroundAppPackage(): Pair<String, String>? {
178186
val time = System.currentTimeMillis()
179-
val events = usageStatsManager.queryEvents(time - 1000 * 100, time)
187+
val events = usageStatsManager.queryEvents(time - 3000, time)
180188
val event = UsageEvents.Event()
181189
var recentApp: Pair<String, String>? = null
190+
var recentAppTime = 0L
182191

183192
while (events.hasNextEvent()) {
184193
events.getNextEvent(event)
185194

186-
if (event.eventType != UsageEvents.Event.ACTIVITY_RESUMED) continue
187-
if (event.className == "dev.pranav.applock.features.lockscreen.ui.PasswordOverlayActivity") continue
195+
Log.d(
196+
TAG,
197+
"${event.eventType} ${event.className} ${event.packageName} ${event.timeStamp} ${event.configuration} ${event.appStandbyBucket}"
198+
)
199+
200+
if (event.eventType != UsageEvents.Event.ACTIVITY_RESUMED && event.eventType != UsageEvents.Event.USER_INTERACTION) continue
188201

189-
if (event.className in AppLockConstants.KNOWN_RECENTS_CLASSES
202+
if (event.packageName == baseContext.packageName || event.className in AppLockConstants.KNOWN_RECENTS_CLASSES) {
203+
recentApp = null
204+
continue
205+
}
206+
207+
if (event.className == "com.android.launcher3.uioverrides.QuickstepLauncher" && event.timeStamp != recentAppTime) {
208+
recentApp = null
209+
AppLockManager.clearTemporarilyUnlockedApp()
210+
continue
211+
}
212+
213+
Log.d(TAG, "recent event ${event.eventType} ${event.className} ${event.packageName}")
214+
215+
if (recentAppTime == event.timeStamp && recentApp?.first != null && appLockRepository.isAppLocked(
216+
recentApp!!.first
217+
)
190218
) {
191219
continue
192220
}
193221

222+
recentAppTime = event.timeStamp
194223
recentApp = Pair(event.packageName, event.className)
195224
}
196225
return recentApp
@@ -260,12 +289,8 @@ class ExperimentalAppLockService : Service() {
260289
createNotificationChannel()
261290
val notification = createNotification()
262291

263-
val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
264-
determineForegroundServiceType()
265-
} else 0
266-
267-
if (type != 0) {
268-
startForeground(NOTIFICATION_ID, notification, type)
292+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
293+
startForeground(NOTIFICATION_ID, notification, determineForegroundServiceType())
269294
} else {
270295
startForeground(NOTIFICATION_ID, notification)
271296
}

0 commit comments

Comments
 (0)