Skip to content

Installer attribution (#444 ask 3/3)#504

Merged
rainxchzed merged 7 commits into
mainfrom
feat/444-installer-attribution
May 4, 2026
Merged

Installer attribution (#444 ask 3/3)#504
rainxchzed merged 7 commits into
mainfrom
feat/444-installer-attribution

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 4, 2026

Third of three PRs for #444. Lets users override the installer-of-record name attached to silent installs (Shizuku, Dhizuku) so apps that gate on installer source can be coaxed into running. See plan in `roadmap/444_C_INSTALLER_ATTRIBUTION.md`.

What's in here

  • Domain: `InstallerAttribution` sealed model (`SystemDefault | Preset(key) | Custom(packageName)`), `PresetKey` enum (PLAY_STORE / FDROID / OBTAINIUM), `InstallerAttributionDefaults.isValidPackageName` regex validator.
  • TweaksRepository: `getInstallerAttribution()` / `setInstallerAttribution()` backed by a new `installer_attribution` DataStore key (string-encoded for forward-compat).
  • AIDL + service impls: `installerPackageName: String?` parameter threaded through `IShizukuInstallerService.installPackage` and `IDhizukuInstallerService.installPackage`.
    • Shizuku path: `pm install -i -S ` when non-null; falls back to existing flag set when null.
    • Dhizuku path: `PackageInstaller.SessionParams.setInstallerPackageName(name)` (Device Owner has the privilege; system honours the claim).
  • Dispatcher: `SilentInstallerDispatcher` observes `getInstallerAttribution()`, resolves to a package name at install time, passes through.
  • UI: New `InstallerAttributionCard` in Tweaks → Installation, only visible when at least one silent backend is READY (per UX review). Radio list of presets + caption with the package name + "Custom…" expander with text field, "Apply" button, and inline regex validation. One-line disclosure under the card naming Play Integrity / banking apps as common gotchas.
  • Naming: "Installer attribution" / "Set installer name" — UX review explicitly rejected "spoof"/"pretend" as adversarial.
  • What's-new bullet shipped in 16.json across 13 locales.

What's deferred (Phase 2)

Plan C originally proposed both global default and per-app override (in `AdvancedAppSettingsBottomSheet`). Per-app needs a Room migration adding `installerAttributionOverride: String?` to `installed_apps` — bigger surface than this PR's scope deserves. Phase 1 ships global-only today. Per-app override + the install-failure-sheet contextual hint land in a follow-up.

Tradeoffs

  • Default off (`SystemDefault`). UX review: defaulting to `com.android.vending` would silently bypass Play Integrity for users who don't know what that means — wrong for the FOSS audience that explicitly opted into this app.
  • Card is hidden (not greyed) when no silent backend is READY. Greyed settings invite "why doesn't this work?" tickets.
  • Encoded as `preset:KEY` / `custom:packageName` rather than JSON-serialized to keep the DataStore migration risk near zero (string only). Deserialization is forward-compat — unknown prefixes resolve to SystemDefault.

Test plan

  • Toggle off (default) → next silent install records shell uid as installer (`pm list packages -i` shows `com.android.shell` for Shizuku, the Dhizuku app for Dhizuku).
  • Pick Play Store preset → install → `pm list packages -i` shows `com.android.vending`.
  • Pick F-Droid preset → install → `org.fdroid.fdroid`.
  • Pick Custom, enter `com.example.installer` → applies. Attempt invalid (`Foo` / ` com.android.vending ` with leading space) → inline error blocks Apply.
  • Hide both Shizuku and Dhizuku → card disappears entirely.
  • Switch between presets → previous selection cleared cleanly, no half-state.
  • Existing Default (system installer) install path unaffected by attribution selection (regression).

Summary by CodeRabbit

  • New Features

    • Installer attribution for silent installs: choose System Default, Play Store, F‑Droid, Obtainium, or enter a custom installer package. UI added to configure, validate, save, persist and apply the choice so silent installs declare the selected installer name for better compatibility with apps that check install source.
  • Chores

    • Localized release notes updated to document the new installer attribution feature.

@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 installer-attribution support: new domain model and validation, persistence in TweaksRepositoryImpl, UI controls and ViewModel handling, expanded AIDL/service APIs and implementations to accept an installer package name, and dispatcher wiring so silent installs pass the chosen installer identity.

Changes

Installer Attribution Feature

Layer / File(s) Summary
Domain Model
core/domain/src/commonMain/kotlin/.../InstallerAttribution.kt, core/domain/src/commonMain/kotlin/.../repository/TweaksRepository.kt
Introduce InstallerAttribution sealed interface (SystemDefault, Preset(PresetKey), Custom), PresetKey enum, resolver and validation helpers; add getInstallerAttribution() and setInstallerAttribution() to TweaksRepository.
Repository Persistence
core/data/src/commonMain/kotlin/.../TweaksRepositoryImpl.kt
Implement getInstallerAttribution() / setInstallerAttribution() with encode/decode logic ("" ⇄ SystemDefault, preset:<key>, custom:<name>). Add INSTALLER_ATTRIBUTION_KEY.
AIDL Interfaces
core/data/src/androidMain/aidl/.../IDhizukuInstallerService.aidl, core/data/src/androidMain/aidl/.../IShizukuInstallerService.aidl
Extend installPackage(...) signatures to accept String installerPackageName.
Service Implementations
core/data/src/androidMain/kotlin/.../services/dhizuku/DhizukuInstallerServiceImpl.kt, core/data/src/androidMain/kotlin/.../services/shizuku/ShizukuInstallerServiceImpl.kt
Accept installerPackageName param; Dhizuku attempts SessionParams.setInstallerPackageName(name) with exception handling; Shizuku adds -i <installerPackageName> to pm install when provided; added logging.
Dispatcher Wiring
core/data/src/androidMain/kotlin/.../services/installer/SilentInstallerDispatcher.kt
Cache installer attribution, observe repository attribution, resolve to package name, and pass it into backend installPackage calls for Shizuku and Dhizuku (DHIZUKU retains expected package/version params).
UI State & Actions
feature/tweaks/presentation/src/commonMain/kotlin/.../TweaksAction.kt, .../TweaksState.kt
Add actions for installer attribution (system default, preset selection, custom toggle/change/save). Extend TweaksState with attribution, custom draft, expanded flag, and error field.
UI Logic
feature/tweaks/presentation/src/commonMain/kotlin/.../TweaksViewModel.kt
Observe getInstallerAttribution() on bootstrap, update state and draft, add action handlers including validation and persistence for custom attribution.
UI Components
feature/tweaks/presentation/src/commonMain/kotlin/.../components/sections/Installation.kt
Add InstallerAttributionCard, CustomInstallerEditor, and AttributionRadioRow to render presets, custom editor, validation, and apply flow when silent installer is available.
UI Resources & Release Notes
core/presentation/src/commonMain/composeResources/values/strings.xml, core/presentation/src/commonMain/composeResources/files/whatsnew/*/16.json
Add string resources for the installer attribution UI and update whatsnew JSON across language variants to document the feature.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as Installation UI
    participant VM as TweaksViewModel
    participant Repo as TweaksRepository
    participant Dispatcher as SilentInstallerDispatcher
    participant Service as Shizuku/Dhizuku Service

    User->>UI: Choose attribution (preset/custom)
    UI->>VM: Dispatch attribution action
    VM->>Repo: setInstallerAttribution(...)
    Repo-->>VM: persisted

    User->>UI: Request silent install
    UI->>Dispatcher: trySilentInstall(...)
    Dispatcher->>Repo: read cached attribution
    Repo-->>Dispatcher: InstallerAttribution
    Dispatcher->>Dispatcher: resolvePackageName()
    Dispatcher->>Service: installPackage(..., installerPackageName)
    Service-->>Dispatcher: result
    Dispatcher-->>UI: success/failure
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

🐰 I nibble prefs beneath the moon so bright,
I name the installer to make installs right.
Preset hop, custom tuck, a tidy little trick,
Silent installs now wear the name you pick.
Hooray — a rabbit’s tiny, clever fix!

🚥 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 'Installer attribution (#444 ask 3/3)' directly matches the main feature being implemented—a new installer attribution system allowing users to override the installer-of-record name for silent installs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/444-installer-attribution

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

🤖 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/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerAttribution.kt`:
- Around line 35-42: The current Validator in InstallerAttributionDefaults
allows uppercase package names because packageNamePattern was created with
RegexOption.IGNORE_CASE; remove that flag so the regex enforces lowercase-only
package segments (keep the pattern string as-is) and keep isValidPackageName
using the same trimmed input. Update packageNamePattern (and any related usage)
to use a case-sensitive regex so values like "Com.Android.Vending" no longer
validate via InstallerAttributionDefaults.isValidPackageName.
- Around line 16-20: resolvePackageName() returns raw Custom.packageName which
can include surrounding whitespace; update the Custom branch to normalize by
trimming before checking/returning (e.g., use packageName.trim().takeIf {
it.isNotBlank() }) so callers like SilentInstallerDispatcher and install
backends never receive leading/trailing spaces while preserving the existing
SystemDefault -> null and Preset -> key.packageName behavior.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt`:
- Line 217: Replace the hard-coded placeholder Text("com.example.installer") in
Installation.kt with a localized string from the core/presentation localization
system (e.g., use the app's localization provider or stringResource and a new
key such as installer_default_package or installationDefaultPackage on the
shared Strings object) and update the 13-language resource files to include
translations for that key; ensure the placeholder uses the localized value
(e.g., Text(localizedStrings.installer_default_package) or
Text(stringResource(R.string.installer_default_package)) rather than the literal
"com.example.installer").

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 303-316: observeInstallerAttribution currently updates
installerAttribution and installerAttributionCustomDraft but never toggles
installerAttributionCustomExpanded, so the custom editor can stay visible when a
non-Custom attribution (e.g., SystemDefault or a preset) is received; inside the
collect block for tweaksRepository.getInstallerAttribution() update the state
copy to set installerAttributionCustomExpanded = if (attribution is
zed.rainxch.core.domain.model.InstallerAttribution.Custom) keep
current.installerAttributionCustomExpanded else false, ensuring the custom
editor collapses whenever the persisted attribution is not Custom while
preserving expansion when it remains Custom.
🪄 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: 4218d9c7-7173-4815-93da-3d943f9084d6

📥 Commits

Reviewing files that changed from the base of the PR and between 3c477cc and b3c5826.

📒 Files selected for processing (26)
  • core/data/src/androidMain/aidl/zed/rainxch/core/data/services/dhizuku/IDhizukuInstallerService.aidl
  • core/data/src/androidMain/aidl/zed/rainxch/core/data/services/shizuku/IShizukuInstallerService.aidl
  • 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
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/shizuku/ShizukuInstallerServiceImpl.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/repository/TweaksRepositoryImpl.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerAttribution.kt
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/repository/TweaksRepository.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/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

onValueChange = { onAction(TweaksAction.OnInstallerAttributionCustomChanged(it)) },
modifier = Modifier.fillMaxWidth(),
label = { Text(stringResource(Res.string.installer_attribution_custom_label)) },
placeholder = { Text("com.example.installer") },
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 | 🟡 Minor | ⚡ Quick win

Localize the custom package placeholder.

com.example.installer is user-facing text, so it should come from resources rather than being hard-coded.

Suggested fix
-            placeholder = { Text("com.example.installer") },
+            placeholder = { Text(stringResource(Res.string.installer_attribution_custom_placeholder)) },

As per coding guidelines, Localize all user-facing strings using the 13-language localization system provided by core/presentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/components/sections/Installation.kt`
at line 217, Replace the hard-coded placeholder Text("com.example.installer") in
Installation.kt with a localized string from the core/presentation localization
system (e.g., use the app's localization provider or stringResource and a new
key such as installer_default_package or installationDefaultPackage on the
shared Strings object) and update the 13-language resource files to include
translations for that key; ensure the placeholder uses the localized value
(e.g., Text(localizedStrings.installer_default_package) or
Text(stringResource(R.string.installer_default_package)) rather than the literal
"com.example.installer").

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
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 661-679: OnInstallerAttributionCustomSave currently launches
tweaksRepository.setInstallerAttribution(...) without error handling and clears
installerAttributionCustomError synchronously, risking crashes and premature
clearing; change the handler so you launch a coroutine with runCatching around
tweaksRepository.setInstallerAttribution(...) (use viewModelScope.launch {
runCatching { tweaksRepository.setInstallerAttribution(...) } .onSuccess {
_state.update { it.copy(installerAttributionCustomError = null) } } .onFailure {
_state.update { it.copy(installerAttributionCustomError = "write_failed" /* or
map error */) }; log the exception } }), ensuring the error state is only
cleared on success and exceptions are caught to avoid uncaught crashes; keep
observeInstallerAttribution() behavior unchanged.
🪄 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: 33f4e205-cf2d-4f58-9dac-0f698285d079

📥 Commits

Reviewing files that changed from the base of the PR and between b3c5826 and 0335085.

📒 Files selected for processing (2)
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerAttribution.kt
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt
✅ Files skipped from review due to trivial changes (1)
  • core/domain/src/commonMain/kotlin/zed/rainxch/core/domain/model/InstallerAttribution.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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt`:
- Around line 615-641: Handlers OnInstallerAttributionSystemDefault and
OnInstallerAttributionPresetSelected call setInstallerAttribution inside
viewModelScope.launch without runCatching and perform the optimistic
_state.update outside the write success path; wrap the setInstallerAttribution
call in runCatching inside the coroutine, move the optimistic state mutation
(clearing error and collapsing installerAttributionCustomExpanded) into the
runCatching.onSuccess block, and handle failures in onFailure (or log) so
IOExceptions from DataStore are caught and the UI state remains consistent;
reference these symbols: OnInstallerAttributionSystemDefault,
OnInstallerAttributionPresetSelected, setInstallerAttribution, _state.update,
installerAttributionCustomExpanded, runCatching, viewModelScope.launch.
- Around line 303-322: The observer in TweaksViewModel that does
viewModelScope.launch { getInstallerAttribution().collect { ... } } can die if
getInstallerAttribution() throws; wrap the flow with .catch { e -> /* log via
logger or _uiState update and optionally emit a fallback/default value */ }
before collect so the coroutine isn't cancelled on error. Apply the same pattern
to the sibling observers (observeShizukuStatus(), observeDhizukuStatus()): use
flow.catch to handle/log errors and supply a sensible fallback or no-op
emission, then continue with collect to update state.
🪄 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: e620fc1c-8daf-4d9c-9c8f-12c46616fd22

📥 Commits

Reviewing files that changed from the base of the PR and between 0335085 and f4d39f4.

📒 Files selected for processing (1)
  • feature/tweaks/presentation/src/commonMain/kotlin/zed/rainxch/tweaks/presentation/TweaksViewModel.kt

@rainxchzed rainxchzed force-pushed the feat/444-installer-attribution branch from 214f588 to 9126258 Compare May 4, 2026 11:38
@rainxchzed rainxchzed force-pushed the feat/444-installer-attribution branch from 9126258 to f68579f Compare May 4, 2026 11:41
@rainxchzed rainxchzed merged commit 15abf03 into main May 4, 2026
@rainxchzed rainxchzed deleted the feat/444-installer-attribution branch May 4, 2026 11:41
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.

1 participant