Architecture and Code Organisation
- Decomposed the monolithic gama_ui.kt (11 948 lines) into a proper multi-file architecture, going from 6 files in v1.2 to 19 files in v1.3
- Extracted GamaPanels.kt to contain all settings sub-panels (2 077 lines)
- Extracted GamaComponents.kt to contain all reusable UI primitives, cards, buttons, and layout helpers (3 107 lines)
- Extracted GamaDialogs.kt to contain all modal dialogs (1 443 lines)
- Extracted GamaTheme.kt to own the theme, colour, and typography system (376 lines)
- Extracted GamaParticles.kt to contain the entire particle and celestial rendering system
- Extracted GamaIntegrations.kt to contain the Tasker/integration panel and its info dialog
- Extracted GamaNotifications.kt to contain notification sending logic and its associated UI
- Promoted the three concrete tile classes out of the combined RendererTileService.kt into their own dedicated files: VulkanTileService.kt, OpenGLTileService.kt and DozeTileService.kt; the original file is now an intentional tombstone
New Features
Backup and Restore System
- Added BackupHelper, a new singleton object that serialises every recognised SharedPreferences key (integers, booleans, floats, longs, strings, and string sets) into a versioned, timestamped JSON file named GAMA_backup_YYYY-MM-DD.json
- Implemented schema validation on import that rejects files lacking the gama_backup_version field, preventing accidental restoration of foreign JSON
- Both export and import are suspend functions that run on Dispatchers.IO so SharedPreferences operations never block the main thread
- Added the corresponding BackupPanel in GamaPanels.kt with full UI for triggering export and import
- Wired SAF file creation and opening launchers (ActivityResultContracts.CreateDocument and OpenDocument) into MainActivity so the backup flows through the official Android file picker
Crash Log Panel
- Added CrashLogPanel inside GamaPanels.kt allowing users to browse and export recent system crash and ANR entries
- Added fetchCrashLogs() to ShizukuHelper, a suspend function that reads dumpsys dropbox --print and parses its output into a list of CrashEntry data class instances
- Implemented a parser that splits on Drop box tag: boundaries, filters for crash/ANR tags, marks SystemUI and HWUI entries as higher priority, truncates body text to 4 000 characters per entry, and returns the 50 most recent entries sorted newest-first
- Added a crash log export path in MainActivity using a dedicated SAF CreateDocument launcher for plain-text files
Renderer Settings Panel
- Extracted renderer-specific settings into a dedicated RendererPanel with its own navigation card in the settings hub
- Added the Kill Launcher toggle inside RendererPanel, off by default, with a visible Xiaomi/Samsung warning in its description text
- Threaded the killLauncher state through to ShizukuHelper's runVulkan and runOpenGL calls as a new killLauncher: Boolean parameter
Doze Quick Settings Tile
- Added DozeTileService, a new Quick Settings tile for forcing Android deep doze on demand
- Implemented the correct two-command entry sequence (dumpsys battery unplug then dumpsys deviceidle force-idle) required on Samsung and Android 15+ devices, and the matching exit sequence (dumpsys deviceidle unforce then dumpsys battery reset)
- Added state verification via dumpsys deviceidle get deep after a 800 ms delay so the tile reflects the actual device state rather than trusting SharedPreferences
- Tile subtitle and active/inactive state update in real time based on the verified device state
Widget Configuration Activity and Background Theme Selector
- Added GamaWidgetConfigureActivity, opened when the user long-presses and configures the home screen widget
- Implemented a Samsung One UI-style bottom sheet with three background modes: Match App Theme (follows the GAMA dark/light setting), Dark (near-black translucent), and Light (near-white translucent)
- Stored the selected mode under a new widget_bg_mode SharedPreferences key
- Designed the sheet with a Samsung-blue apply button, filled-circle radio buttons, a drag handle, and section headers matching the One UI aesthetic
Renderer Guess Without Shizuku
- Added guessRendererWithoutShizuku(prefs) to ShizukuHelper, providing the best available renderer estimate when Shizuku is unavailable
- Used SystemClock.elapsedRealtime() captured at switch time as last_switch_uptime for reboot detection; because elapsedRealtime() resets to near zero on every boot, a stored uptime larger than the current one reliably indicates a reboot occurred and the runtime props were cleared
- Added a backward-compatible fallback for pre-v1.3 installs that have only the old last_switch_time wall-clock key, using currentTimeMillis() - elapsedRealtime() to derive boot time
Boot Renderer Worker via WorkManager
- Replaced the fragile goAsync()-based boot re-apply logic (which had a hard ~10 s deadline that Shizuku almost always outlasted) with a proper WorkManager pipeline
- Implemented BootReceiver to enqueue a OneTimeWorkRequest for BootRendererWorker using ExistingWorkPolicy.KEEP so quick reboots never accumulate duplicate jobs, with exponential backoff starting at 30 seconds
- Implemented BootRendererWorker which polls for Shizuku readiness for up to 90 seconds per attempt, retries up to 5 times, posts a notification on both success and final failure, and deliberately does not overwrite the saved renderer pref to "OpenGL" on failure
- Added handling for Xiaomi and HTC quick-boot broadcast actions (QUICKBOOT_POWERON) in BootReceiver
Skipped boot job enqueuing entirely when the saved renderer is already "OpenGL", since that is Android's default after reboot
Changes to Existing Systems
ShizukuHelper
- Converted runCommand from a blocking function that spawned two Thread objects per call to a proper suspend fun running on Dispatchers.IO
- Reduced the command timeout from 10 seconds to 3 seconds, which cuts the worst-case UI freeze on a hanging Shizuku from 10 s to 3 s while still covering all legitimate slow cases
- Changed stdout/stderr reading from parallel Thread-based reads to sequential reads after waitFor(), which is safe on Dispatchers.IO and eliminates hundreds of short-lived threads during aggressive mode force-stops
- Unified runVulkanSuspend and runOpenGLSuspend into a single private switchRendererSuspend function parameterised by propValue and label, eliminating roughly 140 lines of duplicated code
- Added a private guardedLaunch helper that consolidates the repeated checkBinder/checkPermission guard pattern across runVulkan, runOpenGL, and applyCustomRenderers, and centralises the requestPermissionFallback Toast
- Upgraded renderer detection in getCurrentRenderer from a single getprop call to a three-source chain: the prop GAMA directly sets, a full property scan via getprop | grep -Ei 'hwui|renderer', and finally dumpsys hwui 2>/dev/null | head -20 as a last resort; "Unknown" is only returned when all three sources fail
- Changed the "empty prop" result from returning "Default" to returning "OpenGL", correctly reflecting that Android's default renderer when debug.hwui.renderer is absnt is OpenGL
- Renamed getAllInstalledPackages to getAllPackageNames and rewrote it as a suspend function that uses concurrent async coroutines to drain stdout and stderr before waitFor(), fixing a pipe-buffer overflow bug on MIUI devices with 500+ packages that caused the entire package list to be silently discarded
- Extended the getAllPackageNames timeout to 30 seconds to handle the larger output safely
- Removed com.android.settings and com.google.android.inputmethod.latin from the default app-stop sequence to prevent killing them beforeClearRecentsDialog finishes on MIUI
- Removed the checkVersion method and its parseVersion helper from ShizukuHelper entirely
- Changed exception catch variables from catch (e: Exception) to catch (_: Exception) throughout, following the Kotlin unused-variable convention
MainActivity
- Replaced the single lazily-initialised permissionListener with three properly typed Shizuku listeners registered as class properties: binderReceivedListener (which also auto-requests permission if not already granted on pre-v11 Shizuku), binderDeadListener, and permissionResultListener
- Updated permissionResultListener to trigger a full app restart via FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK after permission is granted, so Shizuku-dependent UI initialises cleanly
- Installed a process-level uncaught exception handler in onCreate before any other setup; it prepends each crash entry (timestamp, thread name, full stack trace) to a crash_log.txt file in filesDir, trims the file to 64 KB, and chains to the default handler so the normal crash dialog and restart behaviour is preserved
- Wired SAF launchers for backup export, backup import, and crash log export inside setContent using rememberLauncherForActivityResult
- Updated GamaUI() call to pass four new lambda parameters: onRequestNotificationPermission, onExportBackup, onImportBackup, and onExportCrashLog
- Changed the notification permission request flow to a proper ActivityResultContracts.RequestPermission launcher triggered by a LaunchedEffect keyed on an integer trigger counter
- Updated onDestroy to correctly remove all three Shizuku listeners using their typed references
GamaWidget
- Replaced the old C colour object with a new W object containing a Samsung-inspired muted palette covering both dark and light variants, including renderer-specific accents (violet for Vulkan, emerald for OpenGL) and a Samsung blue for settings
- Added the WC data class and wc(ctx) factory function that reads widget_bg_mode and theme_preference at runtime to return the correct colour set, making the widget follow the app theme automatically
- Adjusted the six responsive size breakpoints to 73 dp per cell (from 74 dp) to match Samsung One UI cell dimensions more accurately
- Aliased all conflicting Glance imports (Alignment, Box, Column, Row, Spacer, FontWeight, Text, TextAlign) to short prefixed names (GA, GB, GC, GR, GS, GFW, GT, GTA) to resolve Compose namespace collisions
- Replaced SwitchBtn components with new RendererPill components in the widget switch row
- Increased the active renderer font size in the 4×4 layout from 36 sp to 44 sp and updated the label from "CURRENTLY ACTIVE" to "ACTIVE RENDERER"
- Simplified the widget footer copy from "Tap button to switch • Tap card to open" to "Tap card to open GAMA" and added a thin divider above it
- Updated the widget action callback to also write last_switch_time and last_switch_uptime on every switch, supporting the new boot-detection logic
- Fixed a coroutine scope leak by replacing CoroutineScope(Dispatchers.IO).launch in the action callback with withContext(Dispatchers.IO)
TaskerReceiver
- Added a secondary caller-verification layer inside onReceive that checks the calling UID against a TRUSTED_PACKAGES set (the four known Tasker/Locale package names plus GAMA's own package); unknown callers are silently rejected without a Toast to avoid leaking permission structure information
- Added a TASKER_PERMISSION constant to the companion object
- Added a full KDoc security setup block documenting the exact manifest XML needed to declare and guard the receiver, plus the ADB grant command for Tasker
Removed
- Removed AppSelectorPanel and all associated logic; the feature was found to be unreliable on Android 11+ due to PackageManager visibility filtering making the majority of third-party packages invisible without QUERY_ALL_PACKAGES
- Removed the inline ChangelogDialog and ChangelogShimmer composables from the old monolithic file
- Removed the checkVersion and parseVersion version-check network call from ShizukuHelper
- Removed the bgDrawable lookup and per-renderer background drawables from the widget