Skip to content

Fix root mode default installer - #87

Merged
minhnq1-apero merged 3 commits into
mainfrom
fix/issue-85-default-installer
Jul 1, 2026
Merged

Fix root mode default installer#87
minhnq1-apero merged 3 commits into
mainfrom
fix/issue-85-default-installer

Conversation

@minhnq1-apero

@minhnq1-apero minhnq1-apero commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

This PR fixes #85 by ensuring RootService.bind() is called on the main thread and adding HiddenApiBypass for IPackageManager and ServiceManager in the root process.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability when starting the installer by adjusting how the privileged service connection is established, reducing chances of crashes or failed connections on some devices.
    • Enhanced Android P+ compatibility for privileged operations, improving consistency when setting the default installer and initializing required service access.

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
universal-installer Ready Ready Preview, Comment Jul 1, 2026 11:25am

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 600a8922-ee84-4756-b9d1-2fa7381429ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8a45af2 and c1b8caa.

📒 Files selected for processing (2)
  • app/src/main/java/app/pwhs/universalinstaller/privileged/PrivilegedRootService.kt
  • app/src/main/java/app/pwhs/universalinstaller/util/ShizukuDefaultInstaller.kt

📝 Walkthrough

Walkthrough

The changes move root-service binding onto the Android main thread and expand hidden-API exemptions used during privileged service binding and default-installer setup on Android P and above.

Changes

Root privileged service binding fix

Layer / File(s) Summary
Bind RootService on main thread
app/src/main/java/app/pwhs/universalinstaller/presentation/install/controller/FullInstallerBackendFactory.kt
RootService.bind(...) now runs from Handler(Looper.getMainLooper()).post { ... } instead of directly inside the suspend block, while preserving exception propagation.
Hidden API exemptions in onBind
app/src/main/java/app/pwhs/universalinstaller/privileged/PrivilegedRootService.kt, app/src/main/java/app/pwhs/universalinstaller/util/ShizukuDefaultInstaller.kt
onBind and default-installer setup both expand the Android P+ hidden-API exemption list to include ParceledListSlice and BaseParceledListSlice alongside IPackageManager (and ServiceManager in onBind).

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing the root-mode default installer flow.
Linked Issues check ✅ Passed The changes address the root-mode default-installer failure by fixing main-thread service binding and adding hidden-API exemptions.
Out of Scope Changes check ✅ Passed The extra Shizuku hidden-API exemptions are supporting compatibility work and still align with the reported installer fix.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-85-default-installer

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/src/main/java/app/pwhs/universalinstaller/presentation/install/controller/FullInstallerBackendFactory.kt`:
- Around line 267-272: Close the cancellation window around the posted
RootService.bind call in FullInstallerBackendFactory’s binding flow. The issue
is that the Handler.post block can still run after the coroutine has been
cancelled or timed out, causing a late bind after cleanup; update the logic to
guard the posted runnable with the coroutine state and skip RootService.bind
when the continuation is no longer active, using the existing cont/connection
setup to ensure unbind and bind stay in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a4133e7-b581-478c-a341-7b2e7edd856f

📥 Commits

Reviewing files that changed from the base of the PR and between a40be71 and 8a45af2.

📒 Files selected for processing (2)
  • app/src/main/java/app/pwhs/universalinstaller/presentation/install/controller/FullInstallerBackendFactory.kt
  • app/src/main/java/app/pwhs/universalinstaller/privileged/PrivilegedRootService.kt

Comment on lines +267 to +272
android.os.Handler(android.os.Looper.getMainLooper()).post {
try {
RootService.bind(intent, connection)
} catch (t: Throwable) {
if (cont.isActive) cont.resumeWithException(t)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close the cancellation window before the posted bind runs.

If this coroutine is cancelled or times out before the main-thread runnable executes, Line 265 unbinds a connection that has not been bound yet, then Line 269 can still bind afterward. That can leave a root service connection cached from an already-cancelled request.

Proposed fix
-                    cont.invokeOnCancellation {
-                        runCatching { RootService.unbind(connection) }
-                    }
-                    android.os.Handler(android.os.Looper.getMainLooper()).post {
+                    val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
+                    val bindRunnable = Runnable {
+                        if (!cont.isActive) return@Runnable
                         try {
                             RootService.bind(intent, connection)
                         } catch (t: Throwable) {
                             if (cont.isActive) cont.resumeWithException(t)
                         }
                     }
+                    cont.invokeOnCancellation {
+                        mainHandler.removeCallbacks(bindRunnable)
+                        mainHandler.post {
+                            runCatching { RootService.unbind(connection) }
+                        }
+                    }
+                    if (!mainHandler.post(bindRunnable) && cont.isActive) {
+                        cont.resumeWithException(IllegalStateException("Failed to post RootService.bind to main thread"))
+                    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
android.os.Handler(android.os.Looper.getMainLooper()).post {
try {
RootService.bind(intent, connection)
} catch (t: Throwable) {
if (cont.isActive) cont.resumeWithException(t)
}
val mainHandler = android.os.Handler(android.os.Looper.getMainLooper())
val bindRunnable = Runnable {
if (!cont.isActive) return@Runnable
try {
RootService.bind(intent, connection)
} catch (t: Throwable) {
if (cont.isActive) cont.resumeWithException(t)
}
}
cont.invokeOnCancellation {
mainHandler.removeCallbacks(bindRunnable)
mainHandler.post {
runCatching { RootService.unbind(connection) }
}
}
if (!mainHandler.post(bindRunnable) && cont.isActive) {
cont.resumeWithException(IllegalStateException("Failed to post RootService.bind to main thread"))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app/src/main/java/app/pwhs/universalinstaller/presentation/install/controller/FullInstallerBackendFactory.kt`
around lines 267 - 272, Close the cancellation window around the posted
RootService.bind call in FullInstallerBackendFactory’s binding flow. The issue
is that the Handler.post block can still run after the coroutine has been
cancelled or timed out, causing a late bind after cleanup; update the logic to
guard the posted runnable with the coroutine state and skip RootService.bind
when the continuation is no longer active, using the existing cont/connection
setup to ensure unbind and bind stay in sync.

@minhnq1-apero
minhnq1-apero merged commit dfd5980 into main Jul 1, 2026
4 of 5 checks passed
@minhnq1-apero
minhnq1-apero deleted the fix/issue-85-default-installer branch July 1, 2026 11:37
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.

Bug: Set as default installer fails in Root mode

1 participant