Skip to content

Commit ddc12e0

Browse files
Fix FPS pipeline: keep game detection alive outside the app screen, kill timed out Shizuku processes, quote layer names
1 parent 4c918c4 commit ddc12e0

5 files changed

Lines changed: 32 additions & 19 deletions

File tree

‎app/src/main/java/com/example/system/OverlayService.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class OverlayService : Service() {
141141
private fun observePreferencesAndTelemetry() {
142142
// Collect telemetry
143143
GameDeckApp.instance.telemetryManager.start(serviceScope)
144+
UsageDetector.startMonitoring(applicationContext, GameDeckApp.instance.appScope)
144145
serviceScope.launch {
145146
GameDeckApp.instance.telemetryManager.telemetryFlow.collectLatest { metrics ->
146147
currentMetrics.value = metrics

‎app/src/main/java/com/example/system/UsageDetector.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ object UsageDetector {
2121
private var monitoringJob: Job? = null
2222
private var lastForegroundPackage: String? = null
2323
private var activeGamePackage: String? = null
24+
private var seeded = false
2425

2526
fun isUsagePermissionGranted(context: Context): Boolean {
2627
val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as? AppOpsManager ?: return false
@@ -51,7 +52,7 @@ object UsageDetector {
5152
val isAutoDetect = GameDeckApp.instance.repository.isAutoDetectEnabledFlow.first()
5253
if (isAutoDetect && isUsagePermissionGranted(context) && usageStatsManager != null) {
5354
val endTime = System.currentTimeMillis()
54-
val startTime = endTime - 3000L
55+
val startTime = if (seeded) endTime - 3000L else endTime - 120_000L
5556
val events = usageStatsManager.queryEvents(startTime, endTime)
5657
val event = UsageEvents.Event()
5758
var latestPackage: String? = null
@@ -63,6 +64,7 @@ object UsageDetector {
6364
}
6465
}
6566

67+
seeded = true
6668
if (!latestPackage.isNullOrBlank() && latestPackage != lastForegroundPackage) {
6769
handleForegroundAppChanged(context, latestPackage)
6870
}

‎app/src/main/java/com/example/system/collectors/MetricsCollectors.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class FpsCollector(
164164
}
165165

166166
// Query latency
167-
val latencyRes = shizukuBridge.exec("dumpsys SurfaceFlinger --latency \"$layer\"", timeoutMs = 1500L)
167+
val latencyRes = shizukuBridge.exec("dumpsys SurfaceFlinger --latency " + shellQuote(layer), timeoutMs = 1500L)
168168
if (latencyRes.isSuccess) {
169169
val parseResult = SurfaceFlingerParser.parseLatencyOutput(latencyRes.getOrNull() ?: "")
170170
if (parseResult != null) {
@@ -189,6 +189,8 @@ class FpsCollector(
189189
)
190190
}
191191

192+
private fun shellQuote(s: String): String = "'" + s.replace("'", "'\\''") + "'"
193+
192194
private fun getDisplayRefreshRate(): Float {
193195
return try {
194196
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as? DisplayManager

‎app/src/main/java/com/example/system/shizuku/ShizukuBridge.kt‎

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,25 +183,33 @@ class ShizukuBridge(private val context: Context) {
183183
val method = newProcessMethod
184184
?: return@withContext Result.failure(IllegalStateException("Shizuku newProcess method unavailable"))
185185

186+
var process: java.lang.Process? = null
187+
var timedOut = false
186188
try {
187-
withTimeout(timeoutMs) {
188-
val process = method.invoke(null, arrayOf("sh", "-c", command), null, null) as java.lang.Process
189-
val reader = BufferedReader(InputStreamReader(process.inputStream))
190-
val output = StringBuilder()
191-
var line: String?
192-
while (reader.readLine().also { line = it } != null) {
193-
output.append(line).append("\n")
194-
}
195-
reader.close()
196-
val exitCode = process.waitFor()
197-
if (exitCode == 0) {
198-
Result.success(output.toString())
199-
} else {
200-
Result.failure(RuntimeException("Command failed with exit code $exitCode: $command"))
201-
}
189+
process = method.invoke(null, arrayOf("sh", "-c", "$command 2>/dev/null"), null, null) as java.lang.Process
190+
val proc = process
191+
val watchdog = launch {
192+
delay(timeoutMs)
193+
timedOut = true
194+
proc.destroy()
195+
}
196+
val output = try {
197+
proc.inputStream.bufferedReader().use { it.readText() }
198+
} finally {
199+
watchdog.cancel()
200+
}
201+
val exitCode = proc.waitFor()
202+
if (timedOut) {
203+
Result.failure<String>(RuntimeException("Command timed out after ${timeoutMs}ms: $command"))
204+
} else if (exitCode == 0) {
205+
Result.success(output)
206+
} else {
207+
Result.failure<String>(RuntimeException("Command failed with exit code $exitCode: $command"))
202208
}
203209
} catch (e: Throwable) {
204-
Result.failure(e)
210+
Result.failure<String>(e)
211+
} finally {
212+
try { process?.destroy() } catch (_: Throwable) {}
205213
}
206214
}
207215

‎app/src/main/java/com/example/ui/MainViewModel.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
8686
init {
8787
refreshPermissions()
8888
loadInstalledApps()
89-
UsageDetector.startMonitoring(app, viewModelScope)
89+
UsageDetector.startMonitoring(app, app.appScope)
9090
app.telemetryManager.start(viewModelScope)
9191
}
9292

0 commit comments

Comments
 (0)