From 522e755b37ea4dfc7b031761621680f0900fe24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Resul=20=C3=96v=C3=BCr?= <92828257+ovurrsl@users.noreply.github.com> Date: Sat, 17 Oct 2026 05:31:17 +0300 Subject: [PATCH] feat: the session report lists the panel refresh modes, thermal state and hint-session support (#990) --- .../droiddeck/launcher/core/DeviceReport.kt | 48 +++++++++++++++++++ .../launcher/core/DeviceReportPerfTest.kt | 28 +++++++++++ 2 files changed, 76 insertions(+) create mode 100644 app/src/test/java/com/droiddeck/launcher/core/DeviceReportPerfTest.kt diff --git a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt index ef295ce0b..44c19ee61 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt @@ -2,10 +2,15 @@ package com.droiddeck.launcher.core import android.app.ActivityManager import android.content.Context +import android.hardware.display.DisplayManager import android.os.Build import android.os.Environment +import android.os.PerformanceHintManager +import android.os.PowerManager import android.os.StatFs +import android.provider.Settings import android.util.Log +import android.view.Display import com.droiddeck.launcher.gpu.LinuxVulkanDriver import com.droiddeck.launcher.gpu.LinuxVulkanDriverManager import com.droiddeck.launcher.gpu.TurnipDriver @@ -54,6 +59,29 @@ object DeviceReport { else -> "REASON_$reason" } + internal fun thermalStatusName(status: Int): String = when (status) { + 0 -> "NONE" + 1 -> "LIGHT" + 2 -> "MODERATE" + 3 -> "SEVERE" + 4 -> "CRITICAL" + 5 -> "EMERGENCY" + 6 -> "SHUTDOWN" + else -> "STATUS_$status" + } + + internal fun gameModeName(mode: Int): String = when (mode) { + 0 -> "UNSUPPORTED" + 1 -> "STANDARD" + 2 -> "PERFORMANCE" + 3 -> "BATTERY" + 4 -> "CUSTOM" + else -> "MODE_$mode" + } + + internal fun refreshModesSummary(rates: List): String = + rates.map { Math.round(it) }.distinct().sortedDescending().joinToString(", ") { "$it Hz" } + internal fun exitSummary(processName: String?, reason: Int, status: Int, description: String?): String = buildString { append(processName ?: "?").append(" ").append(exitReasonName(reason)).append(" status=").append(status) @@ -124,6 +152,19 @@ object DeviceReport { k("RAM total", FileUtils.sizeToString(mi.totalMem)) k("RAM available", FileUtils.sizeToString(mi.availMem)) } + runCatching { + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) k("Thermal status", thermalStatusName(pm.currentThermalStatus)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + k("Thermal headroom (10 s)", pm.getThermalHeadroom(10).let { if (it.isNaN()) "not reported" else String.format(Locale.US, "%.2f", it) }) + } + k("Power saver", pm.isPowerSaveMode) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val rate = runCatching { context.getSystemService(PerformanceHintManager::class.java)?.preferredUpdateRateNanos }.getOrNull() + k("ADPF hint sessions", if (rate == null || rate <= 0) "not supported" else "supported (preferred update ${rate / 1000} us)") + k("Game Mode", runCatching { context.getSystemService(android.app.GameManager::class.java)?.gameMode }.getOrNull()?.let { gameModeName(it) }) + } runCatching { val fs = StatFs(context.filesDir.path) k("App storage free", FileUtils.sizeToString(fs.availableBlocksLong * fs.blockSizeLong)) @@ -141,6 +182,13 @@ object DeviceReport { h("Display") k("Session output", SessionState.outputSize?.let { "${it.first}x${it.second}" }) k("Session refresh", String.format(Locale.US, "%.2f Hz", SessionState.refreshHz)) + runCatching { + val display = context.getSystemService(DisplayManager::class.java).getDisplay(Display.DEFAULT_DISPLAY) + k("Panel refresh now", String.format(Locale.US, "%.2f Hz", display.refreshRate)) + k("Panel refresh modes", refreshModesSummary(display.supportedModes.map { it.refreshRate })) + } + k("peak_refresh_rate", runCatching { Settings.System.getString(context.contentResolver, "peak_refresh_rate") }.getOrNull() ?: "not set") + k("refresh_rate_mode", runCatching { Settings.Secure.getString(context.contentResolver, "refresh_rate_mode") }.getOrNull() ?: "not set") val panelSize = com.droiddeck.launcher.session.SessionDisplay.panelSize(context) for ((label, mode) in listOf("Resolution (Steam)" to SessionService.MODE_STEAM, "Resolution (Desktop)" to SessionService.MODE_DESKTOP)) { val choice = SessionPrefs.resolutionChoice(context, mode, panelSize) diff --git a/app/src/test/java/com/droiddeck/launcher/core/DeviceReportPerfTest.kt b/app/src/test/java/com/droiddeck/launcher/core/DeviceReportPerfTest.kt new file mode 100644 index 000000000..b9efc4214 --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/core/DeviceReportPerfTest.kt @@ -0,0 +1,28 @@ +package com.droiddeck.launcher.core + +import org.junit.Assert.assertEquals +import org.junit.Test + +class DeviceReportPerfTest { + @Test fun thermalStatusesHaveTheirPlatformNames() { + assertEquals("NONE", DeviceReport.thermalStatusName(0)) + assertEquals("MODERATE", DeviceReport.thermalStatusName(2)) + assertEquals("CRITICAL", DeviceReport.thermalStatusName(4)) + assertEquals("SHUTDOWN", DeviceReport.thermalStatusName(6)) + assertEquals("STATUS_9", DeviceReport.thermalStatusName(9)) + } + + @Test fun gameModesHaveTheirPlatformNames() { + assertEquals("UNSUPPORTED", DeviceReport.gameModeName(0)) + assertEquals("STANDARD", DeviceReport.gameModeName(1)) + assertEquals("PERFORMANCE", DeviceReport.gameModeName(2)) + assertEquals("BATTERY", DeviceReport.gameModeName(3)) + assertEquals("MODE_7", DeviceReport.gameModeName(7)) + } + + @Test fun refreshModesAreRoundedDeduplicatedAndListedFastestFirst() { + assertEquals("120 Hz, 60 Hz", DeviceReport.refreshModesSummary(listOf(60.0f, 120.00001f, 60.00001f))) + assertEquals("60 Hz", DeviceReport.refreshModesSummary(listOf(59.94f))) + assertEquals("", DeviceReport.refreshModesSummary(emptyList())) + } +}