Android: Update dependencies and improve UI feedback

- Update Kotlin and various AndroidX dependencies to stable versions
- Add temperature monitoring with color-coded display in emulation
- Add FPS color indication (red to green based on performance)
- Add legal disclaimer page to initial setup
- Remove x86_64 ABI filter to focus on arm64-v8a
- Adjust thermal and FPS update intervals for consistency
- Clean up redundant dependency declarations

The temperature display now shows both Celsius and Fahrenheit with
color coding based on safe operating ranges [WIP]. FPS counter provides
visual feedback through colors, making performance issues more
immediately apparent to users.
This commit is contained in:
Zephyron 2025-01-13 17:48:02 +10:00
parent 6d5475a9cf
commit cce4abbb0b
6 changed files with 74 additions and 39 deletions

View file

@ -97,23 +97,7 @@ if (ANDROID OR WIN32 OR APPLE)
endif() endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
if (ANDROID AND CITRON_DOWNLOAD_ANDROID_VVL) # Copy the VVL arm64 binary to src/android/app/main/jniLibs. REF: https://github.com/KhronosGroup/Vulkan-ValidationLayers/actions/workflows/vvl.yml
set(vvl_version "sdk-1.3.261.1")
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
if (NOT EXISTS "${vvl_zip_file}")
# Download and extract validation layer release to externals directory
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version}-android.zip"
"${vvl_zip_file}" SHOW_PROGRESS)
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
endif()
# Copy the arm64 binary to src/android/app/main/jniLibs
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
DESTINATION "${vvl_lib_path}")
endif()
if (ANDROID) if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON) set(CMAKE_SKIP_INSTALL_RULES ON)

View file

@ -10,10 +10,10 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "2.1.20-Beta1" kotlin("plugin.serialization") version "1.9.20"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("org.jlleitschuh.gradle.ktlint") version "12.1.2" id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
id("com.github.triplet.play") version "3.12.1" id("com.github.triplet.play") version "3.8.6"
} }
/** /**
@ -180,7 +180,7 @@ android {
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
) )
abiFilters("arm64-v8a", "x86_64") abiFilters("arm64-v8a")
} }
} }
} }
@ -226,24 +226,23 @@ play {
} }
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.15.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.7.0") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.2") implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.8.5") implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1") implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.12.0") implementation("com.google.android.material:material:1.9.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference-ktx:1.2.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("io.coil-kt:coil:2.2.2") implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.3.0") implementation("androidx.window:window:1.2.0-beta03")
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.8.5") implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
implementation("androidx.navigation:navigation-ui-ktx:2.8.5") implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
implementation("info.debatty:java-string-similarity:2.0.0") implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }
fun runGitCommand(command: List<String>): String { fun runGitCommand(command: List<String>): String {

View file

@ -499,10 +499,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val cpuBackend = NativeLibrary.getCpuBackend() val cpuBackend = NativeLibrary.getCpuBackend()
val gpuDriver = NativeLibrary.getGpuDriver() val gpuDriver = NativeLibrary.getGpuDriver()
if (_binding != null) { if (_binding != null) {
// Calculate color based on FPS (red at 0, green at 60)
val fps = perfStats[FPS].toFloat()
val normalizedFps = (fps / 60f).coerceIn(0f, 1f)
// Interpolate between red (0xFFFF0000) and green (0xFF00FF00)
val red = ((1f - normalizedFps) * 255).toInt()
val green = (normalizedFps * 255).toInt()
val color = android.graphics.Color.rgb(red, green, 0)
binding.showFpsText.setTextColor(color)
binding.showFpsText.text = binding.showFpsText.text =
String.format("FPS: %.1f\n%s/%s", perfStats[FPS], cpuBackend, gpuDriver) String.format("FPS: %.1f\n%s/%s", fps, cpuBackend, gpuDriver)
} }
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800) perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 1000)
} }
} }
perfStatsUpdateHandler.post(perfStatsUpdater!!) perfStatsUpdateHandler.post(perfStatsUpdater!!)
@ -528,11 +538,37 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
PowerManager.THERMAL_STATUS_CRITICAL, PowerManager.THERMAL_STATUS_CRITICAL,
PowerManager.THERMAL_STATUS_EMERGENCY, PowerManager.THERMAL_STATUS_EMERGENCY,
PowerManager.THERMAL_STATUS_SHUTDOWN -> "☢️" PowerManager.THERMAL_STATUS_SHUTDOWN -> "☢️"
else -> "🙂" else -> "🙂"
} }
// Get temperature in Celsius from thermal sensor
val temperature = try {
val process = Runtime.getRuntime().exec("cat /sys/class/thermal/thermal_zone0/temp")
val reader = process.inputStream.bufferedReader()
val temp = reader.readLine().toFloat() / 1000f // Convert from millicelsius to celsius
reader.close()
temp
} catch (e: Exception) {
0f
}
// Convert to Fahrenheit
val fahrenheit = (temperature * 9f / 5f) + 32f
if (_binding != null) { if (_binding != null) {
binding.showThermalsText.text = thermalStatus // Color interpolation based on temperature (green at 45°C, red at 85°C)
val normalizedTemp = ((temperature - 45f) / 40f).coerceIn(0f, 1f)
val red = (normalizedTemp * 255).toInt()
val green = ((1f - normalizedTemp) * 255).toInt()
val color = android.graphics.Color.rgb(red, green, 0)
binding.showThermalsText.setTextColor(color)
binding.showThermalsText.text = String.format(
"%s %.1f°C\n%.1f°F",
thermalStatus,
temperature,
fahrenheit
)
} }
thermalStatsUpdateHandler.postDelayed(thermalStatsUpdater!!, 1000) thermalStatsUpdateHandler.postDelayed(thermalStatsUpdater!!, 1000)
} }

View file

@ -98,6 +98,19 @@ class SetupFragment : Fragment() {
val pages = mutableListOf<SetupPage>() val pages = mutableListOf<SetupPage>()
pages.apply { pages.apply {
add(
SetupPage(
R.drawable.ic_check,
R.string.disclaimer_title,
R.string.disclaimer_description,
0,
true,
R.string.accept_and_continue,
{ pageForward() },
false
)
)
add( add(
SetupPage( SetupPage(
R.drawable.ic_citron_title, R.drawable.ic_citron_title,

View file

@ -13,6 +13,9 @@
<string name="welcome">Welcome!</string> <string name="welcome">Welcome!</string>
<string name="welcome_description">Learn how to setup &lt;b>citron&lt;/b> and jump into emulation.</string> <string name="welcome_description">Learn how to setup &lt;b>citron&lt;/b> and jump into emulation.</string>
<string name="get_started">Get started</string> <string name="get_started">Get started</string>
<string name="disclaimer_title">Legal Disclaimer</string>
<string name="disclaimer_description">Welcome to Citron! This application is intended for homebrew development and legitimate backup purposes only. We do not support or advocate piracy. By continuing, you acknowledge that you will only use this application in compliance with all applicable laws and regulations.</string>
<string name="accept_and_continue">Accept &amp; Continue</string>
<string name="keys">Keys</string> <string name="keys">Keys</string>
<string name="keys_description">Select your &lt;b>prod.keys&lt;/b> file with the button below.</string> <string name="keys_description">Select your &lt;b>prod.keys&lt;/b> file with the button below.</string>
<string name="select_keys">Select Keys</string> <string name="select_keys">Select Keys</string>

View file

@ -5,7 +5,7 @@
plugins { plugins {
id("com.android.application") version "8.8.0" apply false id("com.android.application") version "8.8.0" apply false
id("com.android.library") version "8.8.0" apply false id("com.android.library") version "8.8.0" apply false
id("org.jetbrains.kotlin.android") version "2.1.20-Beta1" apply false id("org.jetbrains.kotlin.android") version "1.9.20" apply false
} }
tasks.register("clean").configure { tasks.register("clean").configure {
@ -17,6 +17,6 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.8.5") classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0")
} }
} }