Add Dhizuku support for silent APK installation (#495)#501
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds Dhizuku silent APK installation: new AIDL + service impl, DhizukuServiceManager and status model, SilentInstallerDispatcher replacing the Shizuku wrapper, DI/build/manifest updates, InstallerType/domain additions, and corresponding worker/UI/state/strings/localization changes. ChangesDhizuku Silent Installer Support
Sequence Diagram(s)sequenceDiagram
participant App as App (SilentInstallerDispatcher)
participant Dhizuku as Dhizuku framework
participant Service as DhizukuInstallerServiceImpl
participant PI as Android PackageInstaller
participant BR as BroadcastReceiver
App->>Dhizuku: requestPermission()
Dhizuku-->>App: onPermissionResult(granted/denied)
App->>Dhizuku: bindUserService(DhizukuInstallerServiceImpl)
Dhizuku-->>App: onServiceConnected(IDhizukuInstallerService)
App->>Service: installPackage(ParcelFileDescriptor, fileSize, pkgName, ver)
Service->>PI: createSession(), openWrite(), write APK, fsync
Service->>BR: registerReceiver(install result)
Service->>PI: commit(PendingIntent)
PI-->>BR: send broadcast with status
BR->>Service: deliver result (sets AtomicInteger)
Service-->>App: return STATUS_SUCCESS / STATUS_FAILURE
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt (1)
214-214: 💤 Low valueStale log message references only Shizuku.
The log message says "Installing via Shizuku" but the actual backend could now be Dhizuku. Consider updating to reflect the actual installer type.
📝 Suggested fix
- Logger.d { "AutoUpdateWorker: Installing ${app.appName} via Shizuku" } + Logger.d { "AutoUpdateWorker: Installing ${app.appName} via $installerType" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt` at line 214, The Logger.d call in AutoUpdateWorker is hardcoded to "Installing ${app.appName} via Shizuku"; change it to reflect the actual installer backend (Shizuku or Dhizuku) by using the runtime installer identifier instead of a hardcoded string — e.g., derive installerName from the installer instance or method used (refer to AutoUpdateWorker and whatever variable or method provides the installer, e.g., installer, installerType, or getInstallerName()) and log "Installing ${app.appName} via $installerName" (or similar) so the message matches the real backend.core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt (2)
112-148: ⚖️ Poor tradeoff
runBlockinginside a newThreadblocks the thread unnecessarily.The
uninstallmethod spawns a new thread and immediately blocks it withrunBlocking. This works but is inefficient — you're paying for thread creation only to block it. Sinceuninstall()is not a suspend function (likely due to interface constraints), consider using a class-levelCoroutineScopeor injecting one to launch the coroutine properly.💡 Alternative approach
If refactoring the interface isn't feasible, the current approach works correctly. However, if a
CoroutineScopeis available (e.g., injected or class-level), you could use:override fun uninstall(packageName: String) { scope.launch(Dispatchers.IO) { when (resolveActiveBackend()) { Backend.SHIZUKU -> { // ...existing logic without Thread/runBlocking... } // ... } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt` around lines 112 - 148, The uninstall method currently starts a new Thread and calls runBlocking inside it (in the branches handling Backend.SHIZUKU and Backend.DHIZUKU), which unnecessarily blocks the spawned thread; replace that pattern by launching a coroutine from an injected or class-level CoroutineScope (e.g., scope.launch(Dispatchers.IO)) and perform the service lookup and uninstall logic inside the coroutine instead of Thread/runBlocking — update references in the uninstall implementation around resolveActiveBackend(), shizukuServiceManager.getService(), dhizukuServiceManager.getService(), and androidInstaller.uninstall(packageName) to run on the coroutine so you avoid creating and blocking Threads while preserving the same fallback/error handling semantics.
150-190: 💤 Low value
tryShizukuInstallandtryDhizukuInstallare nearly identical.The only difference is the service manager call. This duplication could be reduced with a generic helper, though the current approach is clear and works correctly.
♻️ Optional consolidation
private suspend fun trySilentInstall( filePath: String, getService: suspend () -> Any?, installFn: (Any, ParcelFileDescriptor, Long) -> Int, tag: String ): InstallOutcome? = try { val service = getService() if (service == null) { Logger.w(TAG) { "$tag service is null, will fall back" } null } else { val result = withContext(Dispatchers.IO) { val file = java.io.File(filePath) val pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) pfd.use { installFn(service, it, file.length()) } } if (result == 0) InstallOutcome.COMPLETED else null } } catch (e: Exception) { Logger.e(TAG) { "$tag install exception: ${e.message}" } null }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt` around lines 150 - 190, tryShizukuInstall and tryDhizukuInstall duplicate the same logic; extract a generic helper (e.g., trySilentInstall) that accepts suspend getService(), an install function (installFn) which takes (service, ParcelFileDescriptor, Long) and returns Int, and a tag for logs; inside the helper perform the same withContext(Dispatchers.IO) file + ParcelFileDescriptor.open(use), call installFn, map result==0 to InstallOutcome.COMPLETED, and catch/ log exceptions; then make tryShizukuInstall call trySilentInstall(getService = shizukuServiceManager::getService, installFn = ::installPackageWrapper, tag = "Shizuku") and tryDhizukuInstall call it with dhizukuServiceManager and "Dhizuku".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl`:
- Around line 1-4: The AIDL interface IDhizukuInstallerService references
ParcelFileDescriptor but lacks the required import; update
IDhizukuInstallerService.aidl to add the explicit AIDL import for
android.os.ParcelFileDescriptor above the package declaration or immediately
after it so the installPackage(in ParcelFileDescriptor pfd, long fileSize)
signature compiles correctly with the AIDL tool.
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt`:
- Around line 52-64: If createSession() succeeds but an exception occurs before
commit(), the code only closes the session and leaves the staged install data
allocated; update the flow around installer.createSession / session (the
variables sessionId, session, installer.createSession, session.openWrite, and
commit()) so that any exception after session creation triggers abandoning the
session: if session is non-null and commit() was not reached, call
session.abandon() (or installer.abandonSession(sessionId) if your API surface
requires) in the error path before closing; ensure the finally block
distinguishes successful commit vs. failure and abandons when needed to prevent
leaking staged install slots and APK bytes.
---
Nitpick comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt`:
- Line 214: The Logger.d call in AutoUpdateWorker is hardcoded to "Installing
${app.appName} via Shizuku"; change it to reflect the actual installer backend
(Shizuku or Dhizuku) by using the runtime installer identifier instead of a
hardcoded string — e.g., derive installerName from the installer instance or
method used (refer to AutoUpdateWorker and whatever variable or method provides
the installer, e.g., installer, installerType, or getInstallerName()) and log
"Installing ${app.appName} via $installerName" (or similar) so the message
matches the real backend.
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt`:
- Around line 112-148: The uninstall method currently starts a new Thread and
calls runBlocking inside it (in the branches handling Backend.SHIZUKU and
Backend.DHIZUKU), which unnecessarily blocks the spawned thread; replace that
pattern by launching a coroutine from an injected or class-level CoroutineScope
(e.g., scope.launch(Dispatchers.IO)) and perform the service lookup and
uninstall logic inside the coroutine instead of Thread/runBlocking — update
references in the uninstall implementation around resolveActiveBackend(),
shizukuServiceManager.getService(), dhizukuServiceManager.getService(), and
androidInstaller.uninstall(packageName) to run on the coroutine so you avoid
creating and blocking Threads while preserving the same fallback/error handling
semantics.
- Around line 150-190: tryShizukuInstall and tryDhizukuInstall duplicate the
same logic; extract a generic helper (e.g., trySilentInstall) that accepts
suspend getService(), an install function (installFn) which takes (service,
ParcelFileDescriptor, Long) and returns Int, and a tag for logs; inside the
helper perform the same withContext(Dispatchers.IO) file +
ParcelFileDescriptor.open(use), call installFn, map result==0 to
InstallOutcome.COMPLETED, and catch/ log exceptions; then make tryShizukuInstall
call trySilentInstall(getService = shizukuServiceManager::getService, installFn
= ::installPackageWrapper, tag = "Shizuku") and tryDhizukuInstall call it with
dhizukuServiceManager and "Dhizuku".
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7c2227aa-e445-4a77-95d8-ca25a33d7826
📒 Files selected for processing (38)
composeApp/src/androidMain/AndroidManifest.xmlcore/data/build.gradle.ktscore/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidlcore/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateCheckWorker.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuServiceManager.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/model/DhizukuStatus.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/AndroidInstallerStatusProvider.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/shizuku/ShizukuInstallerWrapper.ktcore/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopInstallerStatusProvider.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/DhizukuAvailability.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerType.ktcore/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/InstallerStatusProvider.ktcore/presentation/src/commonMain/composeResources/files/whatsnew/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ar/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/bn/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/es/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/fr/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/hi/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/it/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ja/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ko/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/pl/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ru/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/tr/16.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/16.jsoncore/presentation/src/commonMain/composeResources/values/strings.xmlfeature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.ktfeature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.ktfeature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.ktfeature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.ktfeature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.ktfeature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.ktfeature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/feedback/FeedbackViewModel.ktgradle/libs.versions.toml
💤 Files with no reviewable changes (1)
- core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/shizuku/ShizukuInstallerWrapper.kt
| package zed.rainxch.core.data.services.dhizuku; | ||
|
|
||
| interface IDhizukuInstallerService { | ||
| int installPackage(in ParcelFileDescriptor pfd, long fileSize); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the AIDL file
find . -name "IDhizukuInstallerService.aidl" -type fRepository: OpenHub-Store/GitHub-Store
Length of output: 172
🏁 Script executed:
# Also search for other AIDL files to understand project patterns
find . -name "*.aidl" -type f | head -20Repository: OpenHub-Store/GitHub-Store
Length of output: 274
🏁 Script executed:
# Check the content of the specific file if it exists
cat -n "core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl" 2>/dev/null || echo "File not found at expected path"Repository: OpenHub-Store/GitHub-Store
Length of output: 341
🏁 Script executed:
# Check the similar Shizuku AIDL file to compare
cat -n "./core/data/src/androidMain/aidl/zed/rainxch/core/data/services/shizuku/IShizukuInstallerService.aidl"Repository: OpenHub-Store/GitHub-Store
Length of output: 341
🏁 Script executed:
# Search for any AIDL imports in the project to see if there's a pattern
rg "import.*ParcelFileDescriptor" --type aidlRepository: OpenHub-Store/GitHub-Store
Length of output: 52
🏁 Script executed:
# Check for any parcelable declarations in AIDL files
rg "parcelable|import" --type aidlRepository: OpenHub-Store/GitHub-Store
Length of output: 52
🌐 Web query:
AIDL ParcelFileDescriptor import requirement Android
💡 Result:
In AIDL, ParcelFileDescriptor is an Android framework type defined in package android.os, so you should reference it as android.os.ParcelFileDescriptor and import it from android.os when your AIDL file uses it. Example (your .aidl file): package com.example.yourpkg; import android.os.ParcelFileDescriptor; interface IYourService { void doSomething(in ParcelFileDescriptor pfd); } Why the import matters: - The Android AIDL tooling requires you to import every non-built-in type used by the AIDL file, even if that type is in the “same package” as your interface; an import statement is needed for additional types. - ParcelFileDescriptor itself is a parcelable type whose AIDL declaration lives under package android.os (android/os/ParcelFileDescriptor.aidl). So you do need an import in the AIDL source whenever ParcelFileDescriptor appears in an AIDL signature. Common gotcha if you’re seeing “couldn’t find import”: - Ensure the relevant AIDL files are present under the module’s src/main/aidl (and that Gradle is configured to compile that AIDL source set). Otherwise AIDL compilation may fail to resolve imports. - If your custom parcelables (SipProfile, etc.) are failing to resolve, recreate the corresponding SipProfile.aidl file as a known-good workaround and do a clean build; this issue commonly indicates that the AIDL source file doesn’t match what AIDL expects (or Gradle hasn’t picked up the updated AIDL files).
Citations:
- 1: https://source.android.com/docs/core/architecture/aidl/aidl-backends
- 2: https://android.googlesource.com/platform/frameworks/base/+/55269f757e44b25706e26977ca3f69ce079490f5/core/java/android/os/ParcelFileDescriptor.aidl
- 3: https://developer.android.com/develop/background-work/services/aidl
- 4: https://android.googlesource.com/platform/frameworks/base/+/401ad721aded1e3e57ff51b8c2f98578b588ac43/docs/html/guide/developing/tools/aidl.jd
- 5: https://stackoverflow.com/questions/35204269/android-studio-cant-find-aidl-import
🌐 Web query:
AIDL built-in types ParcelFileDescriptor
💡 Result:
ParcelFileDescriptor is a built-in AIDL/Parcelable type: it is declared in the framework as a parcelable in android.os.ParcelFileDescriptor.aidl, and you can use android.os.ParcelFileDescriptor as parameter/return types in AIDL interfaces. In AIDL, ParcelFileDescriptor is treated like any other Parcelable (i.e., you declare it as a type in the AIDL method signature, typically with an import such as import android.os.ParcelFileDescriptor;). The framework definition shows it is a parcelable ParcelFileDescriptor in the android.os package. Grounding note: the Android docs describe ParcelFileDescriptor as a Parcelable that wraps/operates on a file descriptor; and the AIDL source for ParcelFileDescriptor provides the AIDL “built-in” declaration used by the AIDL toolchain. Example AIDL snippet: // IMyService.aidl package com.example; import android.os.ParcelFileDescriptor; interface IMyService { ParcelFileDescriptor openFile(in String path); }
Citations:
- 1: https://android.googlesource.com/platform/frameworks/base/+/b3eeb859f7760785fee6d65fc0bad6ac27f7c614/core/java/android/os/ParcelFileDescriptor.aidl
- 2: https://developer.android.com/reference/android/os/ParcelFileDescriptor
- 3: https://developer.android.com/develop/background-work/services/aidl
- 4: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/IParcelFileDescriptorRetriever.aidl
Add required import for ParcelFileDescriptor in AIDL interface.
AIDL requires explicit imports for framework parcelable types. ParcelFileDescriptor is defined in android.os and must be imported before use, or the AIDL compiler will fail to generate the binder code.
Suggested fix
package zed.rainxch.core.data.services.dhizuku;
+
+import android.os.ParcelFileDescriptor;
interface IDhizukuInstallerService {📝 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.
| package zed.rainxch.core.data.services.dhizuku; | |
| interface IDhizukuInstallerService { | |
| int installPackage(in ParcelFileDescriptor pfd, long fileSize); | |
| package zed.rainxch.core.data.services.dhizuku; | |
| import android.os.ParcelFileDescriptor; | |
| interface IDhizukuInstallerService { | |
| int installPackage(in ParcelFileDescriptor pfd, long fileSize); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl`
around lines 1 - 4, The AIDL interface IDhizukuInstallerService references
ParcelFileDescriptor but lacks the required import; update
IDhizukuInstallerService.aidl to add the explicit AIDL import for
android.os.ParcelFileDescriptor above the package declaration or immediately
after it so the installPackage(in ParcelFileDescriptor pfd, long fileSize)
signature compiles correctly with the AIDL tool.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt`:
- Around line 133-160: The action string derived from packageName.hashCode() is
predictable; generate a high-entropy per-call token and use it consistently for
registration and the PendingIntent. Create a val token =
UUID.randomUUID().toString() and build action =
"$ACTION_UNINSTALL_RESULT.$token" (replace the current packageName.hashCode()
usage), register the BroadcastReceiver with registerInternalReceiver using that
action, and use a requestCode derived from the token (e.g., token.hashCode() or
a SecureRandom int) when calling PendingIntent.getBroadcast so the PendingIntent
and receiver match the new unpredictable action.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 97848fa8-f686-4977-ab53-82a0f2d2f42f
📒 Files selected for processing (4)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt
- core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt`:
- Around line 103-109: The timeout handling after
latch.await(INSTALL_TIMEOUT_SECONDS, TimeUnit.SECONDS) is treating a missing
callback as definitive failure by returning STATUS_FAILURE, which causes
SilentInstallerDispatcher to trigger a fallback concurrently with the pending
Dhizuku operation; change this to return a distinct pending/unknown status
(e.g., STATUS_PENDING or STATUS_UNKNOWN) or perform a package state re-check
before falling back: update the block that currently logs via logE("install
timed out...") and returns STATUS_FAILURE to instead log the timeout, set/return
a pending/unknown status (or invoke a verification method that inspects the
package installation state), and only return failure if the re-check confirms
the install/uninstall did not occur; reference latch.await,
INSTALL_TIMEOUT_SECONDS, logE, STATUS_FAILURE, resultRef.get(),
session.commit(), installer.uninstall(), and SilentInstallerDispatcher when
making the change.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 11015445-eb58-489b-b6e0-7de2c137bde7
📒 Files selected for processing (1)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt
…e reporting failure
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt`:
- Around line 103-111: The timeout fallback only checks package presence and
can’t distinguish an unchanged older version; update the AIDL contract and
callers so the installer can verify package name + version code: add
expectedPackageName and expectedVersionCode parameters to
IDhizukuInstallerService.aidl’s install/verification method, propagate those new
args through SilentInstallerDispatcher.kt when invoking the Dhizuku service, and
change verifyInstallSucceeded (and the timeout branch that uses
INSTALL_TIMEOUT_SECONDS and sessionId) to query the installed package and
confirm its packageName and versionCode match the expected values before
returning STATUS_SUCCESS.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3aae7c34-2eb6-45cc-a02d-c3b89b278a58
📒 Files selected for processing (1)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt
| val finished = latch.await(INSTALL_TIMEOUT_SECONDS, TimeUnit.SECONDS) | ||
| if (!finished) { | ||
| logE("install timed out after ${INSTALL_TIMEOUT_SECONDS}s — verifying package state") | ||
| if (verifyInstallSucceeded(ctx, installer, sessionId)) { | ||
| log("post-timeout verification confirmed install succeeded") | ||
| STATUS_SUCCESS | ||
| } else { | ||
| logE("post-timeout verification could not confirm install") | ||
| STATUS_FAILURE |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that the current timeout path only checks package presence and that
# the binder contract does not carry expected package/version metadata.
fd -p 'IDhizukuInstallerService.aidl' | xargs -r sed -n '1,40p'
fd -p 'DhizukuInstallerServiceImpl.kt' | xargs -r sed -n '100,220p'
rg -n --type=kotlin -C3 '\binstallPackage\s*\('Repository: OpenHub-Store/GitHub-Store
Length of output: 11248
Update install timeout verification must include version check.
The current timeout fallback verifies install success by checking only whether the target package exists. For update flows this is unsafe: if the old version was already installed, verifyInstallSucceeded() returns true even when the new APK never applied. The AIDL contract only carries ParcelFileDescriptor and fileSize, so this service currently cannot reliably distinguish "package was already there" from "this install actually succeeded."
Suggested direction
Extend the AIDL method signature to accept expected package name and version code, then verify against the installed version:
- override fun installPackage(pfd: ParcelFileDescriptor, fileSize: Long): Int {
+ override fun installPackage(
+ pfd: ParcelFileDescriptor,
+ fileSize: Long,
+ expectedPackageName: String,
+ expectedVersionCode: Long,
+ ): Int {- if (verifyInstallSucceeded(ctx, installer, sessionId)) {
+ if (verifyInstallSucceeded(ctx, expectedPackageName, expectedVersionCode)) {- private fun verifyInstallSucceeded(
- ctx: Context,
- installer: PackageInstaller,
- sessionId: Int,
- ): Boolean {
- if (sessionId < 0) return false
- val targetPackage = try {
- installer.getSessionInfo(sessionId)?.appPackageName
- } catch (e: Exception) {
- logE("getSessionInfo() failed during verification", e)
- null
- } ?: return false
- return isPackageInstalled(ctx, targetPackage)
+ private fun verifyInstallSucceeded(
+ ctx: Context,
+ expectedPackageName: String,
+ expectedVersionCode: Long,
+ ): Boolean {
+ val pkg = ctx.packageManager.getPackageInfo(expectedPackageName, 0)
+ return pkg.longVersionCode >= expectedVersionCode
}Update core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl and all callers in SilentInstallerDispatcher.kt.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt`
around lines 103 - 111, The timeout fallback only checks package presence and
can’t distinguish an unchanged older version; update the AIDL contract and
callers so the installer can verify package name + version code: add
expectedPackageName and expectedVersionCode parameters to
IDhizukuInstallerService.aidl’s install/verification method, propagate those new
args through SilentInstallerDispatcher.kt when invoking the Dhizuku service, and
change verifyInstallSucceeded (and the timeout branch that uses
INSTALL_TIMEOUT_SECONDS and sessionId) to query the installed package and
confirm its packageName and versionCode match the expected values before
returning STATUS_SUCCESS.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl (1)
1-7:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
ParcelFileDescriptorstill missing its required AIDL import.AIDL requires that all non-built-in types be explicitly imported; custom Parcelable classes like
ParcelFileDescriptoralways need an import statement. Without it the AIDL compiler cannot resolve the type and stub generation will fail.🛠️ Proposed fix
package zed.rainxch.core.data.services.dhizuku; + +import android.os.ParcelFileDescriptor; interface IDhizukuInstallerService {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl` around lines 1 - 7, The AIDL file is missing the required import for the non-built-in type ParcelFileDescriptor used by IDhizukuInstallerService; add an AIDL import for android.os.ParcelFileDescriptor immediately after the package declaration so the interface (IDhizukuInstallerService) methods installPackage/uninstallPackage/destroy can compile and the AIDL stubs generate correctly, then rebuild to verify generation succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl`:
- Around line 1-7: The AIDL file is missing the required import for the
non-built-in type ParcelFileDescriptor used by IDhizukuInstallerService; add an
AIDL import for android.os.ParcelFileDescriptor immediately after the package
declaration so the interface (IDhizukuInstallerService) methods
installPackage/uninstallPackage/destroy can compile and the AIDL stubs generate
correctly, then rebuild to verify generation succeeds.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 71d7577d-1dfb-4109-9720-b7c0ba3ab705
📒 Files selected for processing (4)
core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidlcore/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.ktcore/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt
- core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt
Closes #495.
Adds Dhizuku as a third silent-install backend alongside the existing Default and Shizuku options. Dhizuku shares Device Owner privileges to GitHub Store, enabling silent installs on OEM ROMs (Xiaomi, OPPO, vivo, Huawei) that aggressively block standard
pm installand ADB-based silent installs.What's in here
InstallerType.DHIZUKUenum entry,DhizukuAvailabilitymodel, and adhizukuAvailabilityflow +requestDhizukuPermission()onInstallerStatusProvider.DhizukuServiceManagermirrors the Shizuku lifecycle pattern;DhizukuInstallerServiceImplruns in the Dhizuku process and usesPackageInstaller(withUSER_ACTION_NOT_REQUIREDon S+) to install silently as Device Owner. AIDL contractIDhizukuInstallerServicematches the existing Shizuku service shape.ShizukuInstallerWrapperwith a singleSilentInstallerDispatcherthat routes through Shizuku, Dhizuku, or the default installer based on the user's selection — cleaner than stacked wrappers as more silent backends get added.PlatformModule.android.ktwires both managers and the dispatcher;AutoUpdateWorker,UpdateCheckWorker,DetailsViewModel,AppsViewModel, andFeedbackViewModelnow treat bothSHIZUKUandDHIZUKUas silent backends (auto-update gate + always-install policy + diagnostics label).com.rosan.dhizuku.permission.APIand a<queries>entry for the Dhizuku content provider so visibility works on Android 11+.16.jsonacross English + 12 locales (1.8.1).Tradeoffs
SilentInstallerDispatcherthat readsInstallerTypeonce — over stacked wrappers. Touches Shizuku files but the routing logic is now linear and adding a fourth backend later (e.g. Sui-only fallback, root) is onewhenarm instead of another wrapper layer.PackageInstallerinside the Dhizuku process rather than shelling out topm install. Device Owner status grants silent install through standardPackageInstallercalls; no shell pipe needed.ActivityThread.currentApplication()is hidden API → resolved via reflection (withAppGlobals.getInitialApplication()fallback) instead of compileOnly stub to avoid dragging another dependency just for this entry point.Test plan
AutoUpdateWorkerruns and silently installs the update.DesktopInstallerStatusProviderexposes both flows asUNAVAILABLE.Summary by CodeRabbit
New Features
Improved
Documentation