From eb63891a2da0540e17f115caec98dd206cf83603 Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Mon, 27 Apr 2026 18:26:13 -0700 Subject: [PATCH 1/3] Whitelist some StrictMode errors for Mediatek, Xiaomi and MIUI devices --- .../app/strictmode/WhitelistRulesTest.kt | 44 ++++++++++++++ .../app/strictmode/WhitelistEngine.kt | 57 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt index aff6724299..4f3fcedc74 100644 --- a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt +++ b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt @@ -1,6 +1,7 @@ package com.itsaky.androidide.app.strictmode import android.os.strictmode.DiskReadViolation +import android.os.strictmode.DiskWriteViolation import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith @@ -98,4 +99,47 @@ class WhitelistRulesTest { ), ) } + + @Test + fun allow_DiskRead_on_MtkBoostFwkIsGameApp() { + assertAllowed( + // @formatter:off + stackTraceElement("java.io.File", "exists"), + stackTraceElement("com.mediatek.boostfwk.utils.Util", "isGameApp"), + stackTraceElement("com.mediatek.boostfwk.utils.TasksUtil", "isGameAPP"), + stackTraceElement("com.mediatek.boostfwk.identify.scroll.ScrollIdentify", "checkAppType"), + stackTraceElement("com.mediatek.boostfwk.identify.scroll.ScrollIdentify", "dispatchScenario"), + // @formatter:on + ) + } + + @Test + fun allow_DiskRead_on_MtkAsyncDrawableCachePutCacheList() { + assertAllowed( + // @formatter:off + stackTraceElement("java.io.File", "exists"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + stackTraceElement("android.content.res.ResourcesImpl", "cacheDrawable"), + // @formatter:on + ) + } + + @Test + fun allow_DiskWrite_on_MtkAsyncDrawableCachePutCacheList() { + assertAllowed( + // @formatter:off + stackTraceElement("libcore.io.IoBridge", "open"), + stackTraceElement("java.io.FileOutputStream", ""), + stackTraceElement("android.app.SharedPreferencesImpl", "createFileOutputStream"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + // @formatter:on + ) + } } diff --git a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt index 02a30e6e66..d9c1ed637a 100644 --- a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt +++ b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt @@ -1,6 +1,7 @@ package com.itsaky.androidide.app.strictmode import android.os.strictmode.DiskReadViolation +import android.os.strictmode.DiskWriteViolation import androidx.annotation.VisibleForTesting import com.itsaky.androidide.app.strictmode.FrameMatcher.Companion.classAndMethod import android.os.strictmode.Violation as StrictModeViolation @@ -220,6 +221,62 @@ object WhitelistEngine { classAndMethod("com.itsaky.androidide.activities.OnboardingActivity", "checkToolsIsInstalled"), ) } + + rule { + ofType() + allow( + """ + On MediaTek devices, BoostFwk's 'Util.isGameApp' is invoked from ScrollIdentify + (via OverScroller's BoostFwkManagerImpl.perfHint) when a RecyclerView/ViewPager2 + is initialized. It checks for a file's existence, resulting in a DiskReadViolation. + Since we can't control when BoostFwk performs scenario detection, we allow this + violation. + """.trimIndent(), + ) + + matchAdjacentFrames( + classAndMethod("java.io.File", "exists"), + classAndMethod("com.mediatek.boostfwk.utils.Util", "isGameApp"), + classAndMethod("com.mediatek.boostfwk.utils.TasksUtil", "isGameAPP"), + classAndMethod("com.mediatek.boostfwk.identify.scroll.ScrollIdentify", "checkAppType"), + ) + } + + rule { + ofType() + allow( + """ + On MediaTek devices, AsyncDrawableCache hooks into Resources.loadDrawable and + synchronously commits to a SharedPreferences-backed cache. This is triggered by + routine layout inflation and produces a DiskReadViolation. Since this caching is + internal to the vendor framework, we allow this violation. + """.trimIndent(), + ) + + matchAdjacentFrames( + classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + ) + } + + rule { + ofType() + allow( + """ + On MediaTek devices, AsyncDrawableCache hooks into Resources.loadDrawable and + synchronously commits to a SharedPreferences-backed cache. The same path also + produces a DiskWriteViolation when the prefs file is written. Since this caching + is internal to the vendor framework, we allow this violation. + """.trimIndent(), + ) + + matchAdjacentFrames( + classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + ) + } } /** From 71d9b8d990a0e69a5ac3e91bb4a433970661128e Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Mon, 27 Apr 2026 19:00:33 -0700 Subject: [PATCH 2/3] Tighten up the whitelist rules to reduce maintenance risk --- .../app/strictmode/WhitelistRulesTest.kt | 15 ++++++++++ .../app/strictmode/WhitelistEngine.kt | 29 +++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt index 4f3fcedc74..d9903c9924 100644 --- a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt +++ b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt @@ -128,6 +128,21 @@ class WhitelistRulesTest { ) } + @Test + fun allow_DiskRead_on_MtkAsyncDrawableCachePutCacheList_OsStatVariant() { + assertAllowed( + // @formatter:off + stackTraceElement("android.system.Os", "stat"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + stackTraceElement("android.content.res.ResourcesImpl", "cacheDrawable"), + // @formatter:on + ) + } + @Test fun allow_DiskWrite_on_MtkAsyncDrawableCachePutCacheList() { assertAllowed( diff --git a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt index d9c1ed637a..eb7d77ccaf 100644 --- a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt +++ b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt @@ -3,6 +3,7 @@ package com.itsaky.androidide.app.strictmode import android.os.strictmode.DiskReadViolation import android.os.strictmode.DiskWriteViolation import androidx.annotation.VisibleForTesting +import com.itsaky.androidide.app.strictmode.FrameMatcher.Companion.anyOf import com.itsaky.androidide.app.strictmode.FrameMatcher.Companion.classAndMethod import android.os.strictmode.Violation as StrictModeViolation @@ -248,15 +249,31 @@ object WhitelistEngine { """ On MediaTek devices, AsyncDrawableCache hooks into Resources.loadDrawable and synchronously commits to a SharedPreferences-backed cache. This is triggered by - routine layout inflation and produces a DiskReadViolation. Since this caching is - internal to the vendor framework, we allow this violation. + routine layout inflation and produces a DiskReadViolation. We anchor this rule + with a java.io disk-access frame at the top and the SharedPreferencesImpl + commit frame immediately adjacent to the vendor chain, so it only fires on the + actual prefs-write path inside this vendor caching code. """.trimIndent(), ) - matchAdjacentFrames( - classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), - classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), - classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + matchAdjacentFramesInOrder( + listOf( + listOf( + anyOf( + classAndMethod("java.io.File", "exists"), + classAndMethod("java.io.FileInputStream", ""), + classAndMethod("java.io.FileOutputStream", ""), + classAndMethod("java.io.RandomAccessFile", ""), + classAndMethod("android.system.Os", "stat"), + ), + ), + listOf( + classAndMethod("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + ), + ), ) } From 1eb825ad11a8002d79793f6f8f66f940fa964c8d Mon Sep 17 00:00:00 2001 From: Hal Eisen Date: Tue, 28 Apr 2026 12:12:28 -0700 Subject: [PATCH 3/3] Refine whitelist rules to be more narrowly targeted --- .../app/strictmode/WhitelistRulesTest.kt | 51 +++++++++++++++++++ .../app/strictmode/WhitelistEngine.kt | 27 +++++++--- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt index d9903c9924..d8e4b65196 100644 --- a/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt +++ b/app/src/androidTest/kotlin/com/itsaky/androidide/app/strictmode/WhitelistRulesTest.kt @@ -151,6 +151,57 @@ class WhitelistRulesTest { stackTraceElement("java.io.FileOutputStream", ""), stackTraceElement("android.app.SharedPreferencesImpl", "createFileOutputStream"), stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + // @formatter:on + ) + } + + @Test + fun allow_DiskWrite_on_MtkAsyncDrawableCache_FileOutputStreamWrite() { + assertAllowed( + // @formatter:off + stackTraceElement("java.io.FileOutputStream", "write"), + stackTraceElement("com.android.internal.util.FastXmlSerializer", "flushBytes"), + stackTraceElement("com.android.internal.util.FastXmlSerializer", "flush"), + stackTraceElement("com.android.internal.util.FastXmlSerializer", "endDocument"), + stackTraceElement("com.android.internal.util.XmlSerializerWrapper", "endDocument"), + stackTraceElement("com.android.internal.util.XmlUtils", "writeMapXml"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + // @formatter:on + ) + } + + @Test + fun allow_DiskWrite_on_MtkAsyncDrawableCache_FileDelete() { + assertAllowed( + // @formatter:off + stackTraceElement("java.io.UnixFileSystem", "delete"), + stackTraceElement("java.io.File", "delete"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), + // @formatter:on + ) + } + + @Test + fun allow_DiskWrite_on_MtkAsyncDrawableCache_OsChmod() { + assertAllowed( + // @formatter:off + stackTraceElement("android.system.Os", "chmod"), + stackTraceElement("android.os.FileUtils", "setPermissions"), + stackTraceElement("android.app.ContextImpl", "setFilePermissionsFromMode"), + stackTraceElement("android.app.SharedPreferencesImpl", "writeToFile"), + stackTraceElement("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), stackTraceElement("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), stackTraceElement("com.mediatek.res.AsyncDrawableCache", "putCacheList"), stackTraceElement("com.mediatek.res.ResOptExtImpl", "putCacheList"), diff --git a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt index eb7d77ccaf..9f396072fe 100644 --- a/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt +++ b/app/src/main/java/com/itsaky/androidide/app/strictmode/WhitelistEngine.kt @@ -283,15 +283,30 @@ object WhitelistEngine { """ On MediaTek devices, AsyncDrawableCache hooks into Resources.loadDrawable and synchronously commits to a SharedPreferences-backed cache. The same path also - produces a DiskWriteViolation when the prefs file is written. Since this caching - is internal to the vendor framework, we allow this violation. + produces a DiskWriteViolation when the prefs file is written. We anchor this + rule with a concrete write-path frame at the top and the SharedPreferencesImpl + commit frame immediately adjacent to the vendor chain, so it only fires on the + actual prefs-write path inside this vendor caching code. """.trimIndent(), ) - matchAdjacentFrames( - classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), - classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), - classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + matchAdjacentFramesInOrder( + listOf( + listOf( + anyOf( + classAndMethod("java.io.FileOutputStream", ""), + classAndMethod("java.io.FileOutputStream", "write"), + classAndMethod("java.io.File", "delete"), + classAndMethod("android.system.Os", "chmod"), + ), + ), + listOf( + classAndMethod("android.app.SharedPreferencesImpl\$EditorImpl", "commit"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "storeDrawableId"), + classAndMethod("com.mediatek.res.AsyncDrawableCache", "putCacheList"), + classAndMethod("com.mediatek.res.ResOptExtImpl", "putCacheList"), + ), + ), ) } }