Skip to content

Add Dhizuku support for silent APK installation (#495)#501

Merged
rainxchzed merged 10 commits into
mainfrom
feat/495-dhizuku-silent-install
May 4, 2026
Merged

Add Dhizuku support for silent APK installation (#495)#501
rainxchzed merged 10 commits into
mainfrom
feat/495-dhizuku-silent-install

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 4, 2026

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 install and ADB-based silent installs.

What's in here

  • Domain. New InstallerType.DHIZUKU enum entry, DhizukuAvailability model, and a dhizukuAvailability flow + requestDhizukuPermission() on InstallerStatusProvider.
  • Data. DhizukuServiceManager mirrors the Shizuku lifecycle pattern; DhizukuInstallerServiceImpl runs in the Dhizuku process and uses PackageInstaller (with USER_ACTION_NOT_REQUIRED on S+) to install silently as Device Owner. AIDL contract IDhizukuInstallerService matches the existing Shizuku service shape.
  • Refactor. Replaced ShizukuInstallerWrapper with a single SilentInstallerDispatcher that routes through Shizuku, Dhizuku, or the default installer based on the user's selection — cleaner than stacked wrappers as more silent backends get added.
  • DI / workers / VMs. PlatformModule.android.kt wires both managers and the dispatcher; AutoUpdateWorker, UpdateCheckWorker, DetailsViewModel, AppsViewModel, and FeedbackViewModel now treat both SHIZUKU and DHIZUKU as silent backends (auto-update gate + always-install policy + diagnostics label).
  • Manifest. Added com.rosan.dhizuku.permission.API and a <queries> entry for the Dhizuku content provider so visibility works on Android 11+.
  • UI. New radio option in Installation settings with status badge (Ready / Permission required / Not active / Not installed), grant-permission button when needed, and shared auto-update toggle visibility — same treatment as Shizuku.
  • What's-new. Bullet announced in 16.json across English + 12 locales (1.8.1).

Tradeoffs

  • Chose option (b) — a single SilentInstallerDispatcher that reads InstallerType once — 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 one when arm instead of another wrapper layer.
  • Used PackageInstaller inside the Dhizuku process rather than shelling out to pm install. Device Owner status grants silent install through standard PackageInstaller calls; no shell pipe needed.
  • ActivityThread.currentApplication() is hidden API → resolved via reflection (with AppGlobals.getInitialApplication() fallback) instead of compileOnly stub to avoid dragging another dependency just for this entry point.

Test plan

  • Install on a device without Dhizuku — option appears, status reads "Not installed", install hint shows.
  • Install Dhizuku, do not activate — status reads "Not active".
  • Activate Dhizuku as Device Owner via ADB — status reads "Permission required", grant button appears.
  • Grant permission — status flips to "Ready", auto-update toggle becomes available.
  • Install an APK from a release — completes without OEM install prompt on a Xiaomi/OPPO/vivo device.
  • Uninstall an installed app via the Apps tab — succeeds silently.
  • Toggle auto-update on, trigger background update check — AutoUpdateWorker runs and silently installs the update.
  • Default and Shizuku paths still behave identically to before (regression check).
  • Desktop (JVM) build still compiles; DesktopInstallerStatusProvider exposes both flows as UNAVAILABLE.

Summary by CodeRabbit

  • New Features

    • Dhizuku added as a silent app‑install option alongside Shizuku (Device Owner bypass on Xiaomi/OPPO/vivo/Huawei).
  • Improved

    • Installer selection shows Dhizuku availability and permission actions; status badges updated.
    • Installer behavior, auto‑update gating, and install/uninstall flows now support both Shizuku and Dhizuku silent backends.
    • UI text updated to reference the currently selected silent installer.
  • Documentation

    • Localized “What’s New” entries updated to announce Dhizuku silent install.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds 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.

Changes

Dhizuku Silent Installer Support

Layer / File(s) Summary
Domain models & interface
core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/{DhizukuAvailability.kt,InstallerType.kt}, core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/InstallerStatusProvider.kt
Adds DhizukuAvailability enum, introduces InstallerType.DHIZUKU, and extends InstallerStatusProvider with dhizukuAvailability: StateFlow and requestDhizukuPermission().
AIDL
core/data/src/androidMain/aidl/.../IDhizukuInstallerService.aidl
New IPC interface IDhizukuInstallerService with installPackage(ParcelFileDescriptor,long,String,long), uninstallPackage(String), and destroy().
Service implementation
core/data/src/androidMain/kotlin/.../dhizuku/DhizukuInstallerServiceImpl.kt
Implements IDhizukuInstallerService.Stub performing PackageInstaller session installs/uninstalls with streaming from PFD, PendingIntent+BroadcastReceiver result handling, timeouts, verification, and cleanup.
Service manager & status
core/data/src/androidMain/kotlin/.../dhizuku/DhizukuServiceManager.kt, .../model/DhizukuStatus.kt
Adds DhizukuServiceManager that computes/refreshes DhizukuStatus StateFlow, requests permission, binds to AIDL service with timeout and binder validation/caching; adds DhizukuStatus enum.
Installer integration
core/data/src/androidMain/kotlin/.../services/installer/{SilentInstallerDispatcher.kt,AndroidInstallerStatusProvider.kt}, core/data/src/jvmMain/.../DesktopInstallerStatusProvider.kt
Removes ShizukuInstallerWrapper; adds SilentInstallerDispatcher routing installs/uninstalls to SHIZUKU/DHIZUKU when READY (fallback to Android installer); status provider now exposes dhizukuAvailability; desktop provider stubs Dhizuku availability.
DI & deps
core/data/src/androidMain/kotlin/.../di/PlatformModule.android.kt, core/data/build.gradle.kts, gradle/libs.versions.toml
Adds dhizuku-api dependency/version, registers and initializes DhizukuServiceManager, wires SilentInstallerDispatcher as the Installer, and updates InstallerStatusProvider wiring.
Workers / auto-update
core/data/src/androidMain/kotlin/.../{AutoUpdateWorker.kt,UpdateCheckWorker.kt}
Auto-update gating/readiness extended to treat InstallerType.DHIZUKU like SHIZUKU for silent background updates; logs now reference selected installer type.
Installer policy routing
feature/apps/.../AppsViewModel.kt, feature/details/.../DetailsViewModel.kt
Maps InstallerType.DHIZUKU to InstallPolicy.AlwaysInstall (same as SHIZUKU) when dispatching installs.
Tweaks UI / state
feature/tweaks/presentation/.../{TweaksAction.kt,TweaksState.kt,TweaksViewModel.kt}, .../components/sections/Installation.kt
Adds OnRequestDhizukuPermission action, dhizukuAvailability state, ViewModel observes Dhizuku status and handles requests; UI adds Dhizuku option, status badge/actions, and combined silentReady logic.
Strings & whatsnew
core/presentation/src/commonMain/composeResources/values/strings.xml, core/presentation/.../whatsnew/*/16.json
Adds Dhizuku installer strings and localized "What's New" bullets announcing Dhizuku silent install; updates auto-update description to reference selected silent installer.
Manifest & app wiring
composeApp/src/androidMain/AndroidManifest.xml
Adds uses-permission for com.rosan.dhizuku.permission.API and <queries> visibility entries for com.rosan.dhizuku and com.rosan.dhizuku.server.provider.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰
I nibble code in moonlit rows,
Dhizuku whispers, permission flows,
Silent installs slip through the night,
Xiaomi, OPPO, vivo, Huawei take flight.
The warren hops — success in sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.52% 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 PR title 'Add Dhizuku support for silent APK installation' accurately and concisely describes the main change—introducing Dhizuku as a new silent installation backend alongside Shizuku and Default.
Linked Issues check ✅ Passed All coding requirements from issue #495 are met: Dhizuku integration [DhizukuServiceManager, DhizukuInstallerServiceImpl], Device Owner-based silent install/uninstall via PackageInstaller, domain models (InstallerType.DHIZUKU, DhizukuAvailability), UI status indicators, permission flow, and auto-update compatibility.
Out of Scope Changes check ✅ Passed All changes are in-scope: Dhizuku service implementation, SilentInstallerDispatcher refactor replacing ShizukuInstallerWrapper, DI wiring, worker/ViewModel updates to handle DHIZUKU, manifest additions, UI components, and multilingual release notes directly support silent APK installation via Dhizuku.

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

✨ 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 feat/495-dhizuku-silent-install

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
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: 2

🧹 Nitpick comments (3)
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt (1)

214-214: 💤 Low value

Stale 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

runBlocking inside a new Thread blocks the thread unnecessarily.

The uninstall method spawns a new thread and immediately blocks it with runBlocking. This works but is inefficient — you're paying for thread creation only to block it. Since uninstall() is not a suspend function (likely due to interface constraints), consider using a class-level CoroutineScope or injecting one to launch the coroutine properly.

💡 Alternative approach

If refactoring the interface isn't feasible, the current approach works correctly. However, if a CoroutineScope is 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

tryShizukuInstall and tryDhizukuInstall are 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

📥 Commits

Reviewing files that changed from the base of the PR and between a806644 and ab728fd.

📒 Files selected for processing (38)
  • composeApp/src/androidMain/AndroidManifest.xml
  • core/data/build.gradle.kts
  • core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/UpdateCheckWorker.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuServiceManager.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/model/DhizukuStatus.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/AndroidInstallerStatusProvider.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/installer/SilentInstallerDispatcher.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/shizuku/ShizukuInstallerWrapper.kt
  • core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopInstallerStatusProvider.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/DhizukuAvailability.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerType.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/system/InstallerStatusProvider.kt
  • core/presentation/src/commonMain/composeResources/files/whatsnew/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/16.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/16.json
  • core/presentation/src/commonMain/composeResources/values/strings.xml
  • feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsViewModel.kt
  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/DetailsViewModel.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksAction.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksState.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/feedback/FeedbackViewModel.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/shizuku/ShizukuInstallerWrapper.kt

Comment on lines +1 to +4
package zed.rainxch.core.data.services.dhizuku;

interface IDhizukuInstallerService {
int installPackage(in ParcelFileDescriptor pfd, long fileSize);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the AIDL file
find . -name "IDhizukuInstallerService.aidl" -type f

Repository: 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 -20

Repository: 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 aidl

Repository: OpenHub-Store/GitHub-Store

Length of output: 52


🏁 Script executed:

# Check for any parcelable declarations in AIDL files
rg "parcelable|import" --type aidl

Repository: 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:


🌐 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:


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.

Suggested change
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.

Copy link
Copy Markdown
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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between ab728fd and de12fb8.

📒 Files selected for processing (4)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AutoUpdateWorker.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt
  • core/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

Copy link
Copy Markdown
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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between de12fb8 and 52c3a50.

📒 Files selected for processing (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt

Copy link
Copy Markdown
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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 52c3a50 and bc3bd8f.

📒 Files selected for processing (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt

Comment on lines +103 to +111
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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.

Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl (1)

1-7: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

ParcelFileDescriptor still missing its required AIDL import.

AIDL requires that all non-built-in types be explicitly imported; custom Parcelable classes like ParcelFileDescriptor always 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

📥 Commits

Reviewing files that changed from the base of the PR and between bc3bd8f and a3b5973.

📒 Files selected for processing (4)
  • core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/di/PlatformModule.android.kt
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/dhizuku/DhizukuInstallerServiceImpl.kt
  • core/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

@rainxchzed rainxchzed merged commit 3c477cc into main May 4, 2026
1 check passed
@rainxchzed rainxchzed deleted the feat/495-dhizuku-silent-install branch May 4, 2026 09: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.

Feature Request: Add Dhizuku support for silent APK installation on restricted devices

1 participant