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.
This commit is contained in:
Seb3thehacker committed 2026-10-11 11:34:06 -07:00
1 parent 33ed2d0653
commit ed331088cc
3 files changed
+33 -6

No files matched your search

+3 -1
View File
@@ -75,8 +75,10 @@
LAUNCHER query below to find, so the setup guide's Android Auto check needs this to see LAUNCHER query below to find, so the setup guide's Android Auto check needs this to see
whether the package is installed at all. --> whether the package is installed at all. -->
<package android:name="com.google.android.projection.gearhead" /> <package android:name="com.google.android.projection.gearhead" />
<!-- Every GrapheneOS install has this; the opt-in usage note uses it to say "GrapheneOS". --> <!-- The opt-in usage note names the OS: GrapheneOS's setup app and /e/OS's app store give
them away (UsageStats.osName). -->
<package android:name="app.grapheneos.setupwizard" /> <package android:name="app.grapheneos.setupwizard" />
<package android:name="foundation.e.apps" />
<intent> <intent>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@@ -205,12 +205,37 @@ object UsageStats {
p.edit().putString(KEY_CARS, cars.toString()).commit() 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) = * Which Android this is: a privacy OS by name, or "Stock" with the phone's brand.
runCatching { context.packageManager.getPackageInfo("app.grapheneos.setupwizard", 0) }.isSuccess *
* 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 { 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}") if (c.phone) put("phone", "${Build.MANUFACTURER} ${Build.MODEL}")
} }
@@ -106,7 +106,7 @@ internal fun ColumnScope.UsageStatsRows() {
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 56.dp, end = 16.dp, top = 12.dp, bottom = 4.dp), 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("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) { Choice("Each car's head unit, model, year, and screen size, and whether it connected", choices.cars) {
choices = choices.copy(cars = it) choices = choices.copy(cars = it)