Fall back to session installer when chosen installer is unavailable

Address #1386

The Shizuku branch of getPreferredInstaller() already fell back to the
session installer, but the guard hasShizukuPerm() called
Shizuku.checkSelfPermission(), which throws when the binder is not alive
(Shizuku disabled). The exception escaped before the fallback could run,
so installs failed instead of falling back.

- Guard hasShizukuPerm() on Shizuku.pingBinder() and swallow failures so
  it returns false (instead of throwing) when Shizuku is unavailable,
  letting the existing fallback run. This also fixes canInstallSilently().
- Detect when any chosen installer resolves to the default and optionally
  inform the user via a toast, gated by notifyOnFallback so cancel/remove
  callers stay silent while real install paths surface it.
This commit is contained in:
Rahul Patel
2026-05-30 05:58:15 +05:30
parent 25d0944e37
commit db9a7723a8
5 changed files with 54 additions and 25 deletions

View File

@@ -174,8 +174,9 @@ class DownloadHelper @Inject constructor(
if (existing != null && existing.versionCode == download.versionCode) {
if (existing.canInstall(context)) {
Log.i(TAG, "${download.packageName} already downloaded, installing directly")
runCatching { appInstaller.getPreferredInstaller().install(existing) }
.onFailure { Log.e(TAG, "Failed to install ${download.packageName}", it) }
runCatching {
appInstaller.getPreferredInstaller(notifyOnFallback = true).install(existing)
}.onFailure { Log.e(TAG, "Failed to install ${download.packageName}", it) }
return
}
if (existing.isActive) {

View File

@@ -25,13 +25,17 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.Constants.PACKAGE_NAME_GMS
import com.aurora.extensions.TAG
import com.aurora.extensions.getUpdateOwnerPackageNameCompat
import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isPAndAbove
import com.aurora.extensions.isSAndAbove
import com.aurora.extensions.toast
import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.installer.ShizukuInstaller.Companion.SHIZUKU_PACKAGE_NAME
import com.aurora.store.data.installer.base.IInstaller
import com.aurora.store.data.model.Installer
@@ -172,8 +176,15 @@ class AppInstaller @Inject constructor(
fun hasShizukuOrSui(context: Context): Boolean = isOAndAbove &&
(PackageUtil.isInstalled(context, SHIZUKU_PACKAGE_NAME) || Sui.isSui())
fun hasShizukuPerm(): Boolean =
Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
// Shizuku.checkSelfPermission() throws when the binder is not alive (Shizuku
// disabled/not running), so guard on pingBinder() and swallow any failure to let
// callers fall back gracefully instead of crashing.
fun hasShizukuPerm(): Boolean = try {
Shizuku.pingBinder() &&
Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED
} catch (_: Exception) {
false
}
fun uninstall(context: Context, packageName: String) {
val intent = Intent().apply {
@@ -196,31 +207,47 @@ class AppInstaller @Inject constructor(
fun getMicroGInstaller(): IInstaller = microGInstaller
fun getPreferredInstaller(): IInstaller = when (getCurrentInstaller(context)) {
Installer.SESSION -> sessionInstaller
/**
* Returns the installer for the user's chosen mode, transparently falling back to the
* default (session) installer when that mode is currently unavailable, e.g. the chosen
* Shizuku installer when Shizuku is disabled.
* @param notifyOnFallback Whether to inform the user via a toast when a fallback happens
*/
fun getPreferredInstaller(notifyOnFallback: Boolean = false): IInstaller {
val selected = getCurrentInstaller(context)
val installer = when (selected) {
Installer.SESSION -> sessionInstaller
Installer.NATIVE -> nativeInstaller
Installer.NATIVE -> nativeInstaller
Installer.ROOT -> if (hasRootAccess()) rootInstaller else defaultInstaller
Installer.ROOT -> if (hasRootAccess()) rootInstaller else defaultInstaller
Installer.SERVICE -> if (hasAuroraService(context)) {
serviceInstaller
} else {
defaultInstaller
Installer.SERVICE -> if (hasAuroraService(context)) {
serviceInstaller
} else {
defaultInstaller
}
Installer.AM -> if (hasAppManager(context)) amInstaller else defaultInstaller
Installer.SHIZUKU -> if (hasShizukuOrSui(context) && hasShizukuPerm()) {
shizukuInstaller
} else {
defaultInstaller
}
Installer.MICROG -> if (hasMicroGInstaller(context)) {
microGInstaller
} else {
defaultInstaller
}
}
Installer.AM -> if (hasAppManager(context)) amInstaller else defaultInstaller
Installer.SHIZUKU -> if (hasShizukuOrSui(context) && hasShizukuPerm()) {
shizukuInstaller
} else {
defaultInstaller
if (selected != Installer.SESSION && installer === defaultInstaller) {
Log.i(TAG, "$selected installer unavailable, falling back to session installer")
if (notifyOnFallback) context.toast(R.string.installer_fallback_session)
}
Installer.MICROG -> if (hasMicroGInstaller(context)) {
microGInstaller
} else {
defaultInstaller
}
return installer
}
}

View File

@@ -252,7 +252,7 @@ class DownloadWorker @AssistedInject constructor(
private suspend fun onSuccess(): Result {
return withContext(NonCancellable) {
return@withContext try {
appInstaller.getPreferredInstaller().install(download)
appInstaller.getPreferredInstaller(notifyOnFallback = true).install(download)
Result.success()
} catch (exception: Exception) {
Log.e(TAG, "Failed to install ${download.packageName}", exception)

View File

@@ -75,7 +75,7 @@ class DownloadsViewModel @Inject constructor(
fun install(download: Download) {
try {
appInstaller.getPreferredInstaller().install(download)
appInstaller.getPreferredInstaller(notifyOnFallback = true).install(download)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install ${download.packageName}", exception)
}