diff --git a/kazeia-android/app/src/main/AndroidManifest.xml b/kazeia-android/app/src/main/AndroidManifest.xml index 930e896..e2ec260 100644 --- a/kazeia-android/app/src/main/AndroidManifest.xml +++ b/kazeia-android/app/src/main/AndroidManifest.xml @@ -9,13 +9,6 @@ - - log(msg) } - MemoryOptimizer.startPeriodicOptimizer(this, serviceScope) { msg -> log(msg) } initializeComponents() } diff --git a/kazeia-android/app/src/main/java/com/kazeia/service/MemoryOptimizer.kt b/kazeia-android/app/src/main/java/com/kazeia/service/MemoryOptimizer.kt deleted file mode 100644 index 3321f2e..0000000 --- a/kazeia-android/app/src/main/java/com/kazeia/service/MemoryOptimizer.kt +++ /dev/null @@ -1,166 +0,0 @@ -package com.kazeia.service - -import android.app.ActivityManager -import android.content.Context -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -/** - * Frees RAM for Kazeia's ML models by nudging non-essential background - * processes out of memory. Uses `ActivityManager.killBackgroundProcesses` - * which: - * - only kills processes that are NOT in foreground or bound to something - * foreground (so no user-visible disruption), - * - leaves the package installed/launchable — the OS will respawn it on - * demand if the user opens the app or a push arrives, - * - requires only `KILL_BACKGROUND_PROCESSES`, a normal permission. - * - * Call [freeRamForModels] once, early in KazeiaService.onCreate, BEFORE - * loading Qwen3-4B (which needs ~3.2 GB resident to avoid swap thrashing). - * - * The list below was picked by reading `dumpsys meminfo` on a fresh boot - * of a OnePlus Pad 3 (ColorOS 16): every package here is either a Google - * background feature Kazeia doesn't use (Wallet, Chromecast, YouTube - * prefetch, …) or an OPLUS feature that re-spawns harmlessly when needed. - * We deliberately avoid Play Services core, input method, launcher, - * system_server, systemui, surfaceflinger, audioserver, HAL services — - * killing any of those would break the UI or audio routing. - */ -object MemoryOptimizer { - - private const val TAG = "MemoryOptimizer" - - /** Packages safe to evict. Order matches approximate RAM savings. */ - private val KILL_TARGETS = listOf( - // Google optional features - "com.google.android.googlequicksearchbox", - "com.google.android.youtube", - "com.google.android.apps.walletnfcrel", - "com.google.android.apps.chromecast.app", - "com.google.android.aicore", - "com.google.android.apps.messaging", - "com.google.android.apps.tachyon", // Google Meet/Duo (resp. 96 MB) - "com.google.android.gm", - "com.google.android.as", - "com.google.android.as.oss", - // Additional targets discovered as respawning heavy after the - // first kill sweep on OnePlus Pad 3 — all user-facing apps that - // we don't need while Kazeia is the active task. - "com.google.android.apps.photos", - "com.google.android.calendar", - "com.android.providers.calendar", - "com.google.android.contacts", - "com.android.vending", // Play Store (resp. 150 MB) - "com.google.android.rkpdapp", - "android.process.acore", - // OPLUS / ColorOS optional features - "com.oneplus.deskclock", - "com.coloros.smartsidebar", - "com.coloros.assistantscreen", - "com.coloros.weather.service", - "com.heytap.mcs", - "com.heytap.accessory", - "com.oplus.cosa", - "com.oplus.pantanal.ums", - "com.oplus.nhs", - "com.oplus.midas", - "com.oplus.olc", - "com.oplus.deepthinker", - "com.oplus.blur", - "com.oplus.statistics.rom", - "com.oplus.powermonitor", - "com.oplus.romupdate", - "com.oplus.location", - "com.oplus.gesture", - "com.oplus.appplatform", - "com.oplus.persist.multimedia", - "com.oplus.nas", - "com.oplus.notificationmanager", - "com.oplus.safecenter", - "com.oplus.securitypermission", - "com.oplus.sau", - // Qualcomm workload profiler — perf hints only, respawns - "com.qualcomm.qti.workloadclassifier" - ) - - /** - * Ask ActivityManager to drop background processes for every package - * in [KILL_TARGETS] that is installed. Returns a (attempted, skipped) - * pair for logging. Safe to call from any thread. - */ - fun freeRamForModels(context: Context, log: (String) -> Unit = {}): Pair { - val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager - ?: run { log("$TAG: ActivityManager unavailable"); return 0 to 0 } - val pm = context.packageManager - val memBefore = readAvailMb(am) - - var killed = 0 - var skipped = 0 - for (pkg in KILL_TARGETS) { - try { - // Only attempt for installed packages — unknown packages - // would silently no-op but still spam the audit log. - pm.getPackageInfo(pkg, 0) - am.killBackgroundProcesses(pkg) - killed++ - } catch (_: android.content.pm.PackageManager.NameNotFoundException) { - skipped++ - } catch (e: SecurityException) { - log("$TAG: killBackgroundProcesses($pkg) denied: ${e.message}") - skipped++ - } catch (e: Exception) { - log("$TAG: killBackgroundProcesses($pkg) failed: ${e.message}") - skipped++ - } - } - - // Give the kernel a moment to reclaim pages before we report. - Thread.sleep(250) - val memAfter = readAvailMb(am) - log("$TAG: killed=$killed skipped=$skipped; avail RAM ${memBefore} MB → ${memAfter} MB (+${memAfter - memBefore} MB)") - return killed to skipped - } - - private fun readAvailMb(am: ActivityManager): Long { - val info = ActivityManager.MemoryInfo() - am.getMemoryInfo(info) - return info.availMem / (1024 * 1024) - } - - /** - * Kick off a background coroutine that re-runs [freeRamForModels] - * every [periodMs]. ColorOS's ActivityManager auto-respawns killed - * apps within 1–3 minutes (observed: quicksearchbox coming back at - * 210 MB, photos/calendar/vending spawning fresh), so a single - * startup sweep isn't enough — periodic pruning is needed while - * Kazeia holds its models in memory. - * - * Cheap (~100 ms total per sweep, mostly the intentional 250 ms - * settle sleep in freeRamForModels), so 60 s cadence is safe. The - * returned [kotlinx.coroutines.Job] should be cancelled by the - * caller when the service stops, to avoid waking the CPU after - * shutdown. - */ - fun startPeriodicOptimizer( - context: Context, - scope: CoroutineScope, - periodMs: Long = 60_000L, - log: (String) -> Unit = {} - ): Job = scope.launch(Dispatchers.IO) { - // First run already happened at service onCreate — skip the - // initial interval so we don't double-sweep on startup. - delay(periodMs) - while (currentCoroutineContext()[Job]?.isActive != false) { - try { - freeRamForModels(context, log) - } catch (e: Exception) { - log("$TAG: periodic sweep failed: ${e.message}") - } - delay(periodMs) - } - } -}