Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!--
Whitelisted use: aggressive-OEM ROMs (Oppo / OnePlus / Realme /
Xiaomi / vivo / Honor) routinely kill background WorkManager
tasks even when the user has scheduled a periodic update check
through GitHub Store's own settings. Surfaced via a one-time
prompt — never required, never re-nagged after dismissal.
-->
<uses-permission
android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
tools:ignore="BatteryLife" />

<uses-permission android:name="com.rosan.dhizuku.permission.API" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ actual val corePlatformModule =
AndroidFileLocationsProvider(context = get())
}

single<zed.rainxch.core.domain.system.AggressiveOemDetector> {
zed.rainxch.core.data.services.AndroidAggressiveOemDetector(context = androidContext())
}

single<PendingInstallNotifier> {
AndroidPendingInstallNotifier(context = androidContext())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package zed.rainxch.core.data.services

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import co.touchlab.kermit.Logger
import zed.rainxch.core.domain.system.AggressiveOemDetector

class AndroidAggressiveOemDetector(
private val context: Context,
) : AggressiveOemDetector {
override fun isAggressiveOem(): Boolean {
val brand = (Build.BRAND ?: "").lowercase()
val manufacturer = (Build.MANUFACTURER ?: "").lowercase()
return AGGRESSIVE_OEMS.any { it in brand || it in manufacturer }
}

override fun isBatteryOptimizationIgnored(): Boolean {
val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager ?: return false
return pm.isIgnoringBatteryOptimizations(context.packageName)
}

override fun openBatteryOptimizationSettings(): Boolean =
// The targeted intent (`ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`)
// bypasses Play Store policy on standalone APKs and lands the user
// directly on the system whitelist toggle — but it's gated by the
// `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission, which Play
// restricts. Fall back to the per-app battery-optimization screen
// if the targeted action throws on a packaged build.
runCatching {
val intent =
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
data = Uri.parse("package:${context.packageName}")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
true
}.getOrElse {
Logger.w(it) {
"AggressiveOemDetector: targeted battery-optimization intent failed; opening generic screen"
}
runCatching {
val fallback =
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(fallback)
true
}.getOrElse { fallbackError ->
Logger.w(fallbackError) {
"AggressiveOemDetector: fallback battery-optimization screen also failed"
}
false
}
}

private companion object {
// Substring matches against `Build.BRAND` / `Build.MANUFACTURER` —
// covers vendor sub-brands (BBK group: Oppo, OnePlus, Realme,
// vivo, iQOO; Xiaomi: Redmi, Poco; Huawei: Honor).
private val AGGRESSIVE_OEMS =
listOf(
"oppo",
"oneplus",
"realme",
"xiaomi",
"redmi",
"poco",
"vivo",
"iqoo",
"huawei",
"honor",
"meizu",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import co.touchlab.kermit.Logger
Expand Down Expand Up @@ -49,6 +50,12 @@ object UpdateScheduler {
OneTimeWorkRequestBuilder<UpdateCheckWorker>()
.setConstraints(constraints)
.setInitialDelay(1, TimeUnit.MINUTES)
// Aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi)
// throttle generic background work hard. Expedited tier
// gets more headroom; fall back to non-expedited when
// the system has no expedited budget left so the work
// still runs eventually rather than failing outright.
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build()

WorkManager
Expand Down Expand Up @@ -108,7 +115,15 @@ object UpdateScheduler {
BackoffPolicy.EXPONENTIAL,
15,
TimeUnit.MINUTES,
).build()
)
// Intentionally NOT expedited: AutoUpdateWorker downloads
// multiple APKs and installs them sequentially, which can
// easily exceed the 10-minute expedited time limit and
// get the worker terminated mid-install. The worker
// already promotes itself to a foreground service via
// setForeground, which gives it the headroom it needs
// without the expedited time cap.
.build()

WorkManager
.getInstance(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,17 @@ class TweaksRepositoryImpl(
}
}

override fun getBatteryOptimizationPromptDismissed(): Flow<Boolean> =
preferences.data.map { prefs ->
prefs[BATTERY_OPT_PROMPT_DISMISSED_KEY] ?: false
}

override suspend fun setBatteryOptimizationPromptDismissed(dismissed: Boolean) {
preferences.edit { prefs ->
prefs[BATTERY_OPT_PROMPT_DISMISSED_KEY] = dismissed
}
}

override fun getLastSeenWhatsNewVersionCode(): Flow<Int?> =
preferences.data.map { prefs ->
prefs[LAST_SEEN_WHATS_NEW_VERSION_CODE_KEY]
Expand Down Expand Up @@ -434,6 +445,7 @@ class TweaksRepositoryImpl(
private val EXTERNAL_MATCH_SEARCH_ENABLED_KEY = booleanPreferencesKey("external_match_search_enabled")
private val EXTERNAL_IMPORT_BANNER_DISMISSED_AT_KEY = intPreferencesKey("external_import_banner_dismissed_at")
private val APK_INSPECT_COACHMARK_SHOWN_KEY = booleanPreferencesKey("apk_inspect_coachmark_shown")
private val BATTERY_OPT_PROMPT_DISMISSED_KEY = booleanPreferencesKey("battery_opt_prompt_dismissed")
private val LAST_SEEN_WHATS_NEW_VERSION_CODE_KEY = intPreferencesKey("last_seen_whats_new_version_code")
private val ANNOUNCEMENTS_DISMISSED_IDS_KEY = stringSetPreferencesKey("announcements_dismissed_ids")
private val ANNOUNCEMENTS_ACKNOWLEDGED_IDS_KEY = stringSetPreferencesKey("announcements_acknowledged_ids")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ actual val corePlatformModule = module {
)
}

single<zed.rainxch.core.domain.system.AggressiveOemDetector> {
zed.rainxch.core.data.services.DesktopAggressiveOemDetector()
}

single<PackageMonitor> {
DesktopPackageMonitor()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zed.rainxch.core.data.services

import zed.rainxch.core.domain.system.AggressiveOemDetector

class DesktopAggressiveOemDetector : AggressiveOemDetector {
override fun isAggressiveOem(): Boolean = false

override fun isBatteryOptimizationIgnored(): Boolean = true

override fun openBatteryOptimizationSettings(): Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ interface TweaksRepository {

suspend fun setApkInspectCoachmarkShown(shown: Boolean)

/**
* One-shot watermark for the battery-optimization prompt on
* aggressive-OEM ROMs (Oppo / OnePlus / Realme / Xiaomi / vivo /
* Honor). `false` until the user has either granted the exemption
* or explicitly dismissed the prompt; flips to `true` afterwards
* and is never re-shown.
*/
fun getBatteryOptimizationPromptDismissed(): Flow<Boolean>

suspend fun setBatteryOptimizationPromptDismissed(dismissed: Boolean)

fun getLastSeenWhatsNewVersionCode(): Flow<Int?>

suspend fun setLastSeenWhatsNewVersionCode(versionCode: Int)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package zed.rainxch.core.domain.system

interface AggressiveOemDetector {
fun isAggressiveOem(): Boolean

fun isBatteryOptimizationIgnored(): Boolean

fun openBatteryOptimizationSettings(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Better version detection — releases that share a numeric version but ship distinct build artifacts are no longer falsely flagged as 'already installed'.",
"Updating multiple apps in a row now works reliably — installs serialize so each one shows the correct APK in the system dialog. Apps screen versions also stay in sync after every install."
]
},
{
"type": "IMPROVED",
"bullets": [
"More reliable background updates on Oppo, OnePlus, Realme, Xiaomi, vivo, and Honor — Tweaks now offers a one-tap battery-optimization shortcut and update workers run with expedited priority."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"اكتشاف أدق للإصدارات — لم يعد المتجر يعتبر الإصدارات التي تحمل نفس الأرقام لكن بيانات بناء مختلفة 'مثبَّتة بالفعل'.",
"تحديث عدة تطبيقات بالتتابع أصبح موثوقاً — التثبيتات تنتظر بعضها فلا يظهر مربع حوار النظام تطبيقاً سابقاً، كما تتحدث الإصدارات في شاشة التطبيقات بشكل صحيح بعد كل تثبيت."
]
},
{
"type": "IMPROVED",
"bullets": [
"تحديثات خلفية أكثر موثوقية على Oppo و OnePlus و Realme و Xiaomi و vivo و Honor — توفّر الإعدادات الآن اختصاراً بنقرة واحدة لاستثناء البطارية، وتعمل مهام التحديث بأولوية مُعجَّلة."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"আরও সঠিক সংস্করণ শনাক্তকরণ — একই সংখ্যা থাকলেও আলাদা বিল্ডের রিলিজগুলো এখন ভুল করে 'ইতিমধ্যে ইনস্টল করা' হিসেবে দেখানো হয় না।",
"একসাথে কয়েকটি অ্যাপ আপডেট এখন নির্ভরযোগ্য — ইনস্টলগুলো ক্রমান্বয়ে চলে, তাই সিস্টেম ডায়ালগ আগের APK দেখায় না। প্রতিটি ইনস্টলের পর অ্যাপস স্ক্রিনে সংস্করণও সঠিকভাবে আপডেট হয়।"
]
},
{
"type": "IMPROVED",
"bullets": [
"Oppo, OnePlus, Realme, Xiaomi, vivo এবং Honor-এ আরও নির্ভরযোগ্য ব্যাকগ্রাউন্ড আপডেট — সেটিংস এখন এক-ট্যাপে ব্যাটারি অপটিমাইজেশনের শর্টকাট দেয় এবং আপডেট ওয়ার্কারগুলো ত্বরান্বিত অগ্রাধিকারে চলে।"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Mejor detección de versiones — las releases que comparten número pero llevan metadatos de build distintos ya no se marcan por error como 'ya instaladas'.",
"Actualizar varias apps seguidas ahora funciona bien — las instalaciones se serializan para que cada diálogo del sistema muestre el APK correcto. Las versiones en la pantalla de apps también se mantienen al día tras cada instalación."
]
},
{
"type": "IMPROVED",
"bullets": [
"Actualizaciones en segundo plano más fiables en Oppo, OnePlus, Realme, Xiaomi, vivo y Honor — Ajustes ofrece ahora un acceso directo a la exclusión de optimización de batería, y las comprobaciones de actualización se ejecutan con prioridad acelerada."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Meilleure détection de version — les releases qui partagent le même numéro mais portent des métadonnées de build différentes ne sont plus signalées à tort comme 'déjà installées'.",
"Mettre à jour plusieurs applis à la suite fonctionne maintenant correctement — les installations se sérialisent pour que chaque boîte de dialogue système affiche le bon APK. Les versions dans l'écran des applis restent aussi à jour après chaque installation."
]
},
{
"type": "IMPROVED",
"bullets": [
"Mises à jour en arrière-plan plus fiables sur Oppo, OnePlus, Realme, Xiaomi, vivo et Honor — Réglages propose désormais un raccourci d'un seul geste pour l'exclusion de l'optimisation de la batterie, et les vérifications de mise à jour s'exécutent en priorité accélérée."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"बेहतर वर्शन डिटेक्शन — एक ही नंबर वाले लेकिन अलग बिल्ड मेटाडेटा वाले रिलीज़ अब गलती से 'पहले से इंस्टॉल' नहीं दिखाए जाते।",
"एक के बाद एक कई ऐप अपडेट करना अब विश्वसनीय है — इंस्टॉल अब क्रम में चलते हैं, इसलिए सिस्टम डायलॉग पिछला APK नहीं दिखाता। हर इंस्टॉल के बाद ऐप्स स्क्रीन में संस्करण भी सही दिखता है।"
]
},
{
"type": "IMPROVED",
"bullets": [
"Oppo, OnePlus, Realme, Xiaomi, vivo और Honor पर अधिक विश्वसनीय बैकग्राउंड अपडेट — सेटिंग्स अब बैटरी ऑप्टिमाइज़ेशन के लिए एक-टैप शॉर्टकट देती है, और अपडेट जॉब्स त्वरित प्राथमिकता के साथ चलते हैं।"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Riconoscimento versioni più preciso — le release con lo stesso numero ma metadati di build diversi non vengono più erroneamente segnalate come 'già installate'.",
"Aggiornare più app di fila ora funziona in modo affidabile — le installazioni vengono serializzate, così ogni finestra di sistema mostra l'APK giusto. Anche le versioni nella schermata App restano aggiornate dopo ogni installazione."
]
},
{
"type": "IMPROVED",
"bullets": [
"Aggiornamenti in background più affidabili su Oppo, OnePlus, Realme, Xiaomi, vivo e Honor — le Impostazioni offrono ora una scorciatoia in un tocco per l'esenzione dall'ottimizzazione della batteria, e i job di aggiornamento girano con priorità accelerata."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"バージョン検出を改善 — 同じバージョン番号でもビルドメタデータが異なるリリースを、誤って『インストール済み』と判定しなくなりました。",
"複数のアプリを連続更新する操作が安定しました — インストールが順番に実行されるため、システムダイアログに前のAPKが表示されることがなくなりました。インストールごとにアプリ画面のバージョンも正しく更新されます。"
]
},
{
"type": "IMPROVED",
"bullets": [
"Oppo / OnePlus / Realme / Xiaomi / vivo / Honor でバックグラウンド更新がより確実に — 設定にバッテリー最適化除外へのワンタップショートカットが追加され、更新ワーカーが優先実行されるようになりました。"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"버전 인식 개선 — 같은 버전 번호여도 빌드 메타데이터가 다른 릴리스를 더 이상 잘못 '이미 설치됨'으로 표시하지 않아요.",
"여러 앱을 연속으로 업데이트해도 안정적으로 동작해요 — 설치가 순차적으로 진행되어 시스템 대화상자에 이전 APK가 표시되지 않아요. 매번 설치 후 앱 화면의 버전도 바르게 갱신돼요."
]
},
{
"type": "IMPROVED",
"bullets": [
"Oppo, OnePlus, Realme, Xiaomi, vivo, Honor에서 백그라운드 업데이트가 더 안정적이에요 — 설정에 배터리 최적화 제외 바로가기가 추가됐고 업데이트 워커가 우선 실행돼요."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Lepsze wykrywanie wersji — wydania o tym samym numerze, ale z różnymi metadanymi build, nie są już błędnie oznaczane jako 'już zainstalowane'.",
"Aktualizowanie wielu aplikacji po kolei działa teraz niezawodnie — instalacje są szeregowane, więc okno dialogowe systemu nie pokazuje już poprzedniego APK. Wersje na ekranie aplikacji również są aktualizowane po każdej instalacji."
]
},
{
"type": "IMPROVED",
"bullets": [
"Bardziej niezawodne aktualizacje w tle na Oppo, OnePlus, Realme, Xiaomi, vivo i Honor — Ustawienia oferują teraz skrót do wyłączenia optymalizacji baterii w jednym kliknięciu, a zadania aktualizacji działają z przyspieszonym priorytetem."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"Точнее определение версий — релизы с одинаковым номером, но разными метаданными сборки больше не помечаются ошибочно как 'уже установленные'.",
"Последовательное обновление нескольких приложений теперь стабильно — установки идут по очереди, поэтому в системном диалоге не показывается предыдущий APK. После каждой установки версия на экране приложений тоже обновляется корректно."
]
},
{
"type": "IMPROVED",
"bullets": [
"Надёжнее обновления в фоне на Oppo, OnePlus, Realme, Xiaomi, vivo и Honor — в настройках появился ярлык в один тап для исключения из оптимизации батареи, а воркеры обновлений работают с ускоренным приоритетом."
]
}
]
}
Loading