From ed331088cced10a18cb13d6cf7acd8f72cd9cd61 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Sun, 11 Oct 2026 11:34:06 -0700 Subject: [PATCH] stats: name the phone's OS instead of only GrapheneOS or other Reports GrapheneOS, CalyxOS, /e/OS, iodeOS, DivestOS, LineageOS, or stock with the brand. --- gearslip/src/main/AndroidManifest.xml | 4 ++- .../gearslip/stats/UsageStats.kt | 33 ++++++++++++++++--- .../gearslip/ui/UsageStatsChoices.kt | 2 +- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/gearslip/src/main/AndroidManifest.xml b/gearslip/src/main/AndroidManifest.xml index 50dfbdb..44ba019 100644 --- a/gearslip/src/main/AndroidManifest.xml +++ b/gearslip/src/main/AndroidManifest.xml @@ -75,8 +75,10 @@ LAUNCHER query below to find, so the setup guide's Android Auto check needs this to see whether the package is installed at all. --> - + + diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/stats/UsageStats.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/stats/UsageStats.kt index 7dd629c..8c2c507 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/stats/UsageStats.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/stats/UsageStats.kt @@ -205,12 +205,37 @@ object UsageStats { p.edit().putString(KEY_CARS, cars.toString()).commit() } - // GrapheneOS reports the same build fields as stock Pixel firmware; its own apps give it away. - private fun isGrapheneOs(context: Context) = - runCatching { context.packageManager.getPackageInfo("app.grapheneos.setupwizard", 0) }.isSuccess + /** + * Which Android this is: a privacy OS by name, or "Stock" with the phone's brand. + * + * GrapheneOS reports the same build fields as stock Pixel firmware, so its own setup app gives + * it away; /e/OS is told apart from the LineageOS it's built on by its app store. The rest set + * build properties of their own. The iodéOS and DivestOS checks are best guesses from their + * build names, not confirmed on a real phone. + */ + fun osName(context: Context): String { + fun installed(pkg: String) = runCatching { context.packageManager.getPackageInfo(pkg, 0) }.isSuccess + val names = listOf("ro.build.flavor", "ro.build.display.id", "ro.lineage.version") + .joinToString(" ") { prop(it) } + " " + Build.DISPLAY + " " + Build.FINGERPRINT + return when { + installed("app.grapheneos.setupwizard") -> "GrapheneOS" + prop("ro.calyxos.version").isNotEmpty() -> "CalyxOS" + installed("foundation.e.apps") -> "/e/OS" + names.contains("iode", ignoreCase = true) -> "iodéOS" + names.contains("divest", ignoreCase = true) -> "DivestOS" + prop("ro.lineage.version").isNotEmpty() -> "LineageOS" + else -> "Stock (${Build.BRAND.replaceFirstChar { it.uppercase() }})" + } + } + + /** A system property, or "" when it isn't set or can't be read. */ + private fun prop(name: String): String = runCatching { + Class.forName("android.os.SystemProperties").getMethod("get", String::class.java) + .invoke(null, name) as String + }.getOrDefault("") private fun details(context: Context, c: Choices) = JSONObject().apply { - if (c.android) put("android", Build.VERSION.RELEASE).put("os", if (isGrapheneOs(context)) "GrapheneOS" else "other") + if (c.android) put("android", Build.VERSION.RELEASE).put("os", osName(context)) if (c.phone) put("phone", "${Build.MANUFACTURER} ${Build.MODEL}") } diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt index 49346f6..a6a5469 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ui/UsageStatsChoices.kt @@ -106,7 +106,7 @@ internal fun ColumnScope.UsageStatsRows() { color = MaterialTheme.colorScheme.primary, modifier = Modifier.padding(start = 56.dp, end = 16.dp, top = 12.dp, bottom = 4.dp), ) - Choice("Android version, and whether it's GrapheneOS", choices.android) { choices = choices.copy(android = it); save() } + Choice("Android version and system, such as GrapheneOS or stock Samsung", choices.android) { choices = choices.copy(android = it); save() } Choice("Phone model", choices.phone) { choices = choices.copy(phone = it); save() } Choice("Each car's head unit, model, year, and screen size, and whether it connected", choices.cars) { choices = choices.copy(cars = it)