Update demo app and alt device names

This commit is contained in:
2025-08-07 02:13:54 -07:00
parent 354f1a6f96
commit e52e2f76fe
13 changed files with 304 additions and 124 deletions

View File

@@ -3,4 +3,5 @@ plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.compose.compiler) apply false
}

View File

@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.compose.compiler)
}
android {
@@ -24,6 +25,12 @@ android {
)
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.get() as String?
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
@@ -36,7 +43,20 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.coil)
implementation(libs.androidx.material)
// Compose UI
implementation(libs.compose.ui)
implementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.compose.foundation)
implementation(libs.compose.runtime)
implementation(libs.compose.material3)
implementation(libs.compose.activity)
// Coil for Compose
implementation(libs.coil.compose)
// Library
implementation(project(":library"))
}

View File

@@ -1,101 +1,201 @@
package dev.oxmc.androiddeviceinfo_demo
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import coil.load
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import dev.oxmc.androiddevicenames.DeviceInfo
import dev.oxmc.androiddevicenames.AndroidInfo
import dev.oxmc.androiddeviceinfo_demo.ui.theme.AndroidDeviceInfoTheme
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var layout: LinearLayout
private lateinit var infoView: TextView
private lateinit var imageView: ImageView
@SuppressLint("SetTextI18n")
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.title = "Android Device Info Demo"
// Main container layout
val rootLayout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
// Add top padding to avoid action bar (56dp is standard action bar height)
setPadding(0, 56, 0, 0)
}
// Content layout that will be centered
layout = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
// This centers the content vertically
gravity = Gravity.CENTER
}
// Internal padding for the content
setPadding(32, 32, 32, 32)
}
infoView = TextView(this).apply {
textSize = 16f
setPadding(0, 16, 0, 16)
}
imageView = ImageView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
600
).apply {
// Add margins around the image
setMargins(0, 16, 0, 16)
}
scaleType = ImageView.ScaleType.FIT_CENTER
adjustViewBounds = true
}
layout.addView(imageView)
layout.addView(infoView)
rootLayout.addView(layout)
setContentView(rootLayout)
lifecycleScope.launch {
try {
val (deviceName, manufacturer, model, codename, imageUrl) =
DeviceInfo.getDeviceInfo(this@MainActivity)
val deviceType = AndroidInfo.getType(this@MainActivity)
infoView.text = """
Device Name: $deviceName
Manufacturer: $manufacturer
Model: $model
Codename: $codename
Device Type: $deviceType
Android Version: ${AndroidInfo.Version.release} (SDK ${AndroidInfo.Version.sdkInt})
Image URL: ${imageUrl ?: "N/A"}
""".trimIndent()
imageUrl?.let { url ->
imageView.load(url) {
crossfade(true)
}
setContent {
AndroidDeviceInfoTheme {
Surface(modifier = Modifier.fillMaxSize()) {
DeviceInfoScreen()
}
} catch (e: Exception) {
infoView.text = "Error loading device info: ${e.message}"
}
}
}
}
@SuppressLint("HardwareIds", "MissingPermission")
@Composable
fun DeviceInfoScreen() {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
var deviceName: String? by remember { mutableStateOf("Unknown") }
var manufacturer: String? by remember { mutableStateOf("Unknown") }
var model: String? by remember { mutableStateOf("Unknown") }
var codename: String? by remember { mutableStateOf("Unknown") }
var imageUrl by remember { mutableStateOf<String?>(null) }
var deviceType by remember { mutableStateOf("Unknown") }
var androidVersion by remember { mutableStateOf("Unknown") }
var serial by remember { mutableStateOf("Unknown") }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
coroutineScope.launch {
try {
val (name, manu, mod, code, url) = DeviceInfo.getDeviceInfo(context)
deviceName = name
manufacturer = manu
model = mod
codename = code
imageUrl = url
deviceType = AndroidInfo.getType(context).toString()
androidVersion = "${AndroidInfo.Version.release} (SDK ${AndroidInfo.Version.sdkInt})"
serial = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Build.SERIAL.takeIf { it != Build.UNKNOWN }
?: Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
} else {
try {
Build.getSerial()
} catch (e: SecurityException) {
Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
}
}
} catch (_: Exception) { }
isLoading = false
}
}
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
Text(
text = "Loading device info...",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(top = 25.dp),
textAlign = TextAlign.Center
)
}
}
} else {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
shape = MaterialTheme.shapes.extraLarge,
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface
)
) {
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Device Info", style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.height(16.dp))
imageUrl?.let {
AsyncImage(
model = it,
contentDescription = "Device Image",
modifier = Modifier
.size(180.dp)
.padding(8.dp)
)
} ?: run {
Image(
painter = painterResource(id = R.drawable.unknown_device),
contentDescription = "Unknown device",
modifier = Modifier
.size(180.dp)
.padding(8.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
InfoText("Name", deviceName)
InfoText("Manufacturer", manufacturer)
InfoText("Model", model)
InfoText("Codename", codename)
InfoText("Serial", serial)
InfoText("Type", deviceType)
InfoText("Android", androidVersion)
// If the device has no image or the information is unknown, show a message
if (imageUrl == null || deviceName == "Unknown") {
Text(
text = "Unable to determine device details from the model. If you know the model or would like to upload this information to improve detection, please use the button below:",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
coroutineScope.launch {
DeviceInfo.reportDeviceInfo(
context,
deviceName ?: "Unknown",
manufacturer ?: "Unknown",
model ?: "Unknown",
codename ?: "Unknown"
)
}
},
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "Upload Phone Data",
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
}
}
}
}
}
}
}
@Composable
fun InfoText(label: String, value: String?) {
Text(
text = "$label: $value",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(vertical = 2.dp),
textAlign = TextAlign.Center
)
}

View File

@@ -0,0 +1,42 @@
package dev.oxmc.androiddeviceinfo_demo.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val LightColors = lightColorScheme(
primary = Color(0xFFB388FF), // Lavender
onPrimary = Color.White,
secondary = Color(0xFF7C4DFF), // Purple
onSecondary = Color.White,
background = Color(0xFFD2AEDC), // Light Lavender
onBackground = Color(0xFF2A003F), // Dark Purple
surface = Color.White,
onSurface = Color(0xFF2A003F) // Dark Purple
)
private val DarkColors = darkColorScheme(
primary = Color(0xFFD1B3FF), // Light Lavender (for contrast)
onPrimary = Color.Black,
secondary = Color(0xFFB388FF),
onSecondary = Color.Black,
background = Color(0xFF1B0030),
onBackground = Color.White,
surface = Color(0xFF2A003F),
onSurface = Color.White
)
@Composable
fun AndroidDeviceInfoTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (useDarkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colorScheme,
typography = Typography(),
content = content
)
}

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14zM12,6.72c-1.96,0 -3.5,1.52 -3.5,3.47h1.75c0,-0.93 0.82,-1.75 1.75,-1.75s1.75,0.82 1.75,1.75c0,1.75 -2.63,1.57 -2.63,4.45h1.76c0,-1.96 2.62,-2.19 2.62,-4.45 0,-1.96 -1.54,-3.47 -3.5,-3.47zM11.12,15.52h1.76v1.76h-1.76z"/>
</vector>

View File

@@ -1,15 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.AndroidDeviceInfo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.AndroidDeviceInfo" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Enable light icons if status bar is dark -->
<item name="android:windowLightStatusBar">false</item>
</style>
</resources>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="lavender_500">#B388FF</color>
<color name="lavender_700">#7C4DFF</color>
<color name="lavender_on_primary">#FFFFFF</color>
<color name="lavender_secondary">#7C4DFF</color>
<color name="lavender_on_secondary">#FFFFFF</color>
<color name="lavender_background">#F3E5F5</color>
<color name="lavender_on_background">#2A003F</color>
</resources>

View File

@@ -1,14 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.AndroidDeviceNames" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.AndroidDeviceInfo" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<item name="colorPrimary">@color/lavender_500</item>
<item name="colorPrimaryVariant">@color/lavender_700</item>
<item name="colorOnPrimary">@color/lavender_on_primary</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<item name="colorSecondary">@color/lavender_secondary</item>
<item name="colorSecondaryVariant">@color/lavender_700</item>
<item name="colorOnSecondary">@color/lavender_on_secondary</item>
<!-- Background color -->
<item name="android:colorBackground">@color/lavender_background</item>
<item name="colorOnBackground">@color/lavender_on_background</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
</style>

View File

@@ -3,17 +3,30 @@ agp = "8.12.0"
kotlin = "2.0.21"
coreKtx = "1.10.1"
appcompat = "1.6.1"
material = "1.12.0"
material = "1.9.0"
material3 = "1.2.1"
compose = "1.6.0"
coil = "2.5.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
# Compose core libraries
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose" }
compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "compose" }
compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "compose" }
compose-activity = { group = "androidx.activity", name = "activity-compose", version = "1.8.2" }
# Coil for Compose
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
android-library = { id = "com.android.library", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

View File

@@ -33,5 +33,4 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
}

View File

@@ -41,6 +41,12 @@ object AltDeviceNames {
Triple(listOf("CPH2195"), listOf("Oppo"), listOf("")) to "OPPO A54 5G",
Triple(listOf("OP595DL1", "CPH2583"), listOf("Oppo"), listOf("")) to "OnePlus 12",
// Emulators/sdk
Triple(listOf("sdk_gphone_x86"), listOf("Google"), listOf("")) to "Android SDK (x86)",
Triple(listOf("sdk_gphone_x86_64"), listOf("Google"), listOf("")) to "Android SDK (x86_64)",
Triple(listOf("sdk_gphone_arm64"), listOf("Google"), listOf("")) to "Android SDK (arm64)",
Triple(listOf("sdk_gphone_arm"), listOf("Google"), listOf("")) to "Android SDK (arm)",
// Google
Triple(listOf("Pixel 6"), listOf("Google"), listOf("")) to "Pixel 6",

View File

@@ -19,6 +19,6 @@ dependencyResolutionManagement {
}
}
rootProject.name = "AndroidDeviceNames"
rootProject.name = "AndroidDeviceInfo"
include(":demo")
include(":library")