Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<uses-permission android:name="com.rosan.dhizuku.permission.API" />

<!--
Fallback visibility for installer-source classification when
QUERY_ALL_PACKAGES is denied on Android 11+. We need to recognise
Expand All @@ -31,6 +33,8 @@
<package android:name="org.fdroid.fdroid" />
<package android:name="com.android.vending" />
<package android:name="com.aurora.store" />
<package android:name="com.rosan.dhizuku" />
<provider android:authorities="com.rosan.dhizuku.server.provider" />
</queries>

<application
Expand Down
1 change: 1 addition & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ kotlin {
implementation(libs.androidx.work.runtime)
implementation(libs.shizuku.api)
implementation(libs.shizuku.provider)
implementation(libs.dhizuku.api)
compileOnly(libs.hidden.api.stub)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package zed.rainxch.core.data.services.dhizuku;

interface IDhizukuInstallerService {
int installPackage(in ParcelFileDescriptor pfd, long fileSize, String expectedPackageName, long expectedVersionCode);
int uninstallPackage(String packageName);
void destroy();
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import zed.rainxch.core.data.services.LocalizationManager
import zed.rainxch.core.data.services.external.AndroidExternalAppScanner
import zed.rainxch.core.data.services.external.InstallerSourceClassifier
import zed.rainxch.core.data.services.external.ManifestHintExtractor
import zed.rainxch.core.data.services.shizuku.AndroidInstallerStatusProvider
import zed.rainxch.core.data.services.shizuku.ShizukuInstallerWrapper
import zed.rainxch.core.data.services.dhizuku.DhizukuServiceManager
import zed.rainxch.core.data.services.installer.AndroidInstallerStatusProvider
import zed.rainxch.core.data.services.installer.SilentInstallerDispatcher
import zed.rainxch.core.data.services.shizuku.ShizukuServiceManager
import zed.rainxch.core.data.utils.AndroidAppLauncher
import zed.rainxch.core.data.utils.AndroidBrowserHelper
Expand Down Expand Up @@ -74,22 +75,33 @@ actual val corePlatformModule =
).also { it.initialize() }
}

// Installer — the ShizukuInstallerWrapper is the public Installer singleton.
// It delegates to AndroidInstaller by default, intercepting with Shizuku when enabled.
// DhizukuServiceManager — manages Dhizuku lifecycle, permissions, service binding
single {
DhizukuServiceManager(
context = androidContext(),
).also { it.initialize() }
}

// Installer — SilentInstallerDispatcher routes through the user's selected
// silent backend (Shizuku, Dhizuku) or falls back to the standard installer.
single<Installer> {
ShizukuInstallerWrapper(
SilentInstallerDispatcher(
androidContext = androidContext(),
androidInstaller = get<AndroidInstaller>(),
shizukuServiceManager = get(),
dhizukuServiceManager = get(),
tweaksRepository = get(),
).also { wrapper ->
wrapper.observeInstallerPreference(get<CoroutineScope>())
scope = get<CoroutineScope>(),
).also { dispatcher ->
dispatcher.observeInstallerPreference()
}
}

// InstallerStatusProvider — exposes Shizuku availability to the UI layer
// InstallerStatusProvider — exposes both Shizuku and Dhizuku availability to UI
single<InstallerStatusProvider> {
AndroidInstallerStatusProvider(
shizukuServiceManager = get(),
dhizukuServiceManager = get(),
scope = get(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import co.touchlab.kermit.Logger
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import zed.rainxch.core.data.services.dhizuku.DhizukuServiceManager
import zed.rainxch.core.data.services.dhizuku.model.DhizukuStatus
import zed.rainxch.core.data.services.shizuku.ShizukuServiceManager
import zed.rainxch.core.data.services.shizuku.model.ShizukuStatus
import zed.rainxch.core.domain.model.InstalledApp
Expand Down Expand Up @@ -45,6 +47,7 @@ class AutoUpdateWorker(
private val downloader: Downloader by inject()
private val tweaksRepository: TweaksRepository by inject()
private val shizukuServiceManager: ShizukuServiceManager by inject()
private val dhizukuServiceManager: DhizukuServiceManager by inject()

override suspend fun doWork(): Result {
return try {
Expand All @@ -53,12 +56,21 @@ class AutoUpdateWorker(
val autoUpdateEnabled = tweaksRepository.getAutoUpdateEnabled().first()
val installerType = tweaksRepository.getInstallerType().first()

shizukuServiceManager.refreshStatus()
val shizukuReady = shizukuServiceManager.status.value == ShizukuStatus.READY
val silentReady = when (installerType) {
InstallerType.SHIZUKU -> {
shizukuServiceManager.refreshStatus()
shizukuServiceManager.status.value == ShizukuStatus.READY
}
InstallerType.DHIZUKU -> {
dhizukuServiceManager.refreshStatus()
dhizukuServiceManager.status.value == DhizukuStatus.READY
}
InstallerType.DEFAULT -> false
}

if (!autoUpdateEnabled || installerType != InstallerType.SHIZUKU || !shizukuReady) {
if (!autoUpdateEnabled || !silentReady) {
Logger.i {
"AutoUpdateWorker: Conditions not met (autoUpdate=$autoUpdateEnabled, installer=$installerType, shizuku=$shizukuReady), skipping"
"AutoUpdateWorker: Conditions not met (autoUpdate=$autoUpdateEnabled, installer=$installerType, silentReady=$silentReady), skipping"
}
return Result.success()
}
Expand Down Expand Up @@ -199,7 +211,8 @@ class AutoUpdateWorker(
)
}

Logger.d { "AutoUpdateWorker: Installing ${app.appName} via Shizuku" }
val installerLabel = tweaksRepository.getInstallerType().first().name
Logger.d { "AutoUpdateWorker: Installing ${app.appName} via $installerLabel" }
try {
installer.install(filePath, ext)
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ class UpdateCheckWorker(
val autoUpdateEnabled = tweaksRepository.getAutoUpdateEnabled().first()
val installerType = tweaksRepository.getInstallerType().first()

if (autoUpdateEnabled && installerType == InstallerType.SHIZUKU) {
val isSilentBackend = installerType == InstallerType.SHIZUKU || installerType == InstallerType.DHIZUKU
if (autoUpdateEnabled && isSilentBackend) {
Logger.i {
"UpdateCheckWorker: Auto-update enabled with Shizuku, scheduling AutoUpdateWorker for ${appsWithUpdates.size} apps"
"UpdateCheckWorker: Auto-update enabled with $installerType, scheduling AutoUpdateWorker for ${appsWithUpdates.size} apps"
}
UpdateScheduler.scheduleAutoUpdate(applicationContext)
} else {
Expand Down
Loading