Skip to content

ADFA-2814 | Mitigate StrictMode disk I/O during APK install and quick build#934

Merged
jatezzz merged 3 commits intostagefrom
fix/ADFA-2814-strictmode-io-apk-install-build
Feb 6, 2026
Merged

ADFA-2814 | Mitigate StrictMode disk I/O during APK install and quick build#934
jatezzz merged 3 commits intostagefrom
fix/ADFA-2814-strictmode-io-apk-install-build

Conversation

@jatezzz
Copy link
Collaborator

@jatezzz jatezzz commented Feb 5, 2026

Description

This PR mitigates StrictMode DiskRead/DiskWrite violations by moving filesystem checks and APK metadata resolution off the main thread. It ensures APK validation, output listing parsing, and plugin artifact discovery run on Dispatchers.IO, reducing UI-thread disk access during quick build and APK installation flows.

Details

  • StrictMode violations addressed:

    • File.exists()/isFile/extension checks during APK install
    • APK output listing parsing (ApkMetadata.findApkFile) and APK existence check
    • Plugin .cgp output discovery after build
  • Installation session logic now runs within Dispatchers.IO and safely abandons sessions on failure.

Before changes

image

After changes

image

Ticket

ADFA-2814

Observation

This is a minimal-change mitigation focused on eliminating disk access on the main thread in the critical build/install paths. Some StrictMode logs originating from system PackageInstaller internals may still appear, but app-level file IO in these flows is now moved off main.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Release Notes

Features & Improvements

  • Mitigated StrictMode DiskRead/DiskWrite violations during APK installation and quick-build flows by moving filesystem operations off the main thread.
  • Moved APK validation and APK existence checks to Dispatchers.IO.
  • Moved APK installation session management (session creation, opening, adding APK, commit) to Dispatchers.IO with explicit try/catch/finally resource cleanup and session abandonment on failure.
  • Moved plugin artifact discovery to Dispatchers.IO (isPluginProject check and .cgp file lookup).
  • Moved APK output listing parsing (ApkMetadata.findApkFile) to Dispatchers.IO.
  • Improved error handling and ensured sessions are closed or abandoned within IO context to avoid main-thread disk I/O.

Risks & Best Practices Considerations

  • ⚠️ Incomplete mitigation: StrictMode warnings originating from system PackageInstaller internals may still appear; this PR targets app-level disk access only.
  • ⚠️ Thread-safety: Session lifecycle now runs on background threads — verify no race conditions between UI-thread updates and background session operations.
  • ⚠️ Minimal-change tradeoffs: Implemented as a focused mitigation (quick rollout) rather than a full I/O refactor; additional follow-up work may be needed for comprehensive optimization.
  • ⚠️ Error propagation: Session abandonment and error handling were moved into the IO block; confirm caller-level error handling behavior remains correct.
  • ⚠️ Coverage: Ensure all code paths that interact with sessions or APK files are consistently migrated to IO context to avoid regressions.

Files Modified

  • app/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt (+18/-9)
  • app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt (+10/-5)

Walkthrough

Moved blocking file/project checks into suspended IO contexts and consolidated APK package session creation, add/commit, error handling, abandonment, and session close inside a single withContext(Dispatchers.IO) block with try/catch/finally.

Changes

Cohort / File(s) Summary
ViewModel — IO dispatcher migration
app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt
Wrapped blocking checks and file lookups (isPluginProject, findPluginCgpFile, ApkMetadata.findApkFile, apkFile.exists()) in withContext(Dispatchers.IO) to run off the main coroutine dispatcher.
APK installer — session lifecycle & error handling
app/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt
Moved session creation/open/add/commit into a single withContext(Dispatchers.IO) block; added try/catch/finally to call installer.abandonSession(sessionId) on inner failures and ensure session.close(); simplified outer failure path to logging.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant VM as ViewModel
participant FS as FileSystem (IO)
participant Installer as ApkInstaller
participant PKG as PackageInstaller.Session
VM->>FS: withContext(Dispatchers.IO): isPluginProject, findPluginCgpFile, findApkFile, exists()
alt APK found & valid
VM->>Installer: installUsingSession(apk)
Installer->>FS: withContext(Dispatchers.IO): createSession() → sessionId
Installer->>PKG: openSession(sessionId)
PKG-->>Installer: session
Installer->>PKG: addToSession(apkStream)
alt add succeeds
Installer->>PKG: commit() (within try)
PKG-->>Installer: commit result
else add fails
Installer->>Installer: installer.abandonSession(sessionId) (catch)
Installer-->>VM: throw error
end
Installer->>PKG: close() (finally)
else APK missing/invalid
VM-->>VM: short-circuit / report error
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • itsaky-adfa
  • jomen-adfa
  • dara-abijo-adfa

Poem

🐰 I hop to IO where checks reside,
Sessions born and errors tried.
Commit or abandon, close with grace,
A tidy build in coroutine space. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: mitigating StrictMode disk I/O violations during APK install and quick build, which aligns with the core objective of moving filesystem checks off the main thread.
Description check ✅ Passed The description is well-related to the changeset, explaining the StrictMode violations being addressed, the specific changes made to move I/O operations to Dispatchers.IO, and including before/after evidence.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ADFA-2814-strictmode-io-apk-install-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt`:
- Around line 98-107: The catch block in the installation flow (around
addToSession(session, apk) and session.commit(callback!!.intentSender) in
ApkInstaller.kt) currently swallows exceptions by only calling runCatching {
installer.abandonSession(sessionId) } and not rethrowing; change it so that
after attempting to abandon the session (installer.abandonSession(sessionId))
you rethrow the original exception so failures propagate to callers (i.e.,
capture the caught Throwable, call installer.abandonSession(sessionId) safely,
then rethrow the captured exception); ensure session.close() still runs in the
finally and preserve use of getCallbackIntent/context/intent/sessionId
references.

@jatezzz jatezzz force-pushed the fix/ADFA-2814-strictmode-io-apk-install-build branch from 854b1f4 to 6b2f7f5 Compare February 5, 2026 19:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt`:
- Around line 99-108: The createSession/openSession sequence can orphan a
session if openSession throws; ensure sessionId is abandoned whenever
createSession succeeded but openSession or later operations fail by moving
openSession inside the try or adding a nested try/catch that calls
installer.abandonSession(sessionId) on any exception before session is opened;
reference installer.createSession, installer.openSession, sessionId,
installer.abandonSession, addToSession, session.commit and session.close so that
any exception between createSession and obtaining a usable session triggers
installer.abandonSession(sessionId) and resources are cleaned up.
🧹 Nitpick comments (2)
app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt (1)

85-92: Consider combining sequential IO operations into a single context switch.

Both findApkFile and exists() are disk operations that could be combined to avoid two context switches. However, this is a minor optimization and the current implementation is clear.

♻️ Optional: Combine into single IO block
-			val apkFile = withContext(Dispatchers.IO) {
-				ApkMetadata.findApkFile(outputListingFile)
-			} ?: throw RuntimeException("No APK found in output listing file.")
-
-			val apkExists = withContext(Dispatchers.IO) { apkFile.exists() }
-			if (!apkExists) {
-				throw RuntimeException("APK file specified does not exist: $apkFile")
-			}
+			val apkFile = withContext(Dispatchers.IO) {
+				val file = ApkMetadata.findApkFile(outputListingFile)
+					?: throw RuntimeException("No APK found in output listing file.")
+				if (!file.exists()) {
+					throw RuntimeException("APK file specified does not exist: $file")
+				}
+				file
+			}
app/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt (1)

101-104: Consider explicit null handling for callback.

getCallbackIntent() returns PendingIntent?, and callback!! will throw a NullPointerException if the PendingIntent creation fails. While the exception is caught and the session is properly abandoned, the error message would be misleading (NPE instead of a meaningful installation error).

♻️ Suggested improvement
 				val callback = getCallbackIntent(context, intent, sessionId)
+				if (callback == null) {
+					throw IllegalStateException("Failed to create installation callback intent")
+				}
 				try {
 					addToSession(session, apk)
-					session.commit(callback!!.intentSender)
+					session.commit(callback.intentSender)

@jatezzz jatezzz force-pushed the fix/ADFA-2814-strictmode-io-apk-install-build branch from 1e5a420 to 325ef58 Compare February 6, 2026 13:19
@jatezzz jatezzz force-pushed the fix/ADFA-2814-strictmode-io-apk-install-build branch from fa9e3df to a1ecfab Compare February 6, 2026 21:07
@jatezzz jatezzz merged commit a37becb into stage Feb 6, 2026
2 checks passed
@jatezzz jatezzz deleted the fix/ADFA-2814-strictmode-io-apk-install-build branch February 6, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants