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 != null && existing.versionCode == download.versionCode) {
if (existing.canInstall(context)) { if (existing.canInstall(context)) {
Log.i(TAG, "${download.packageName} already downloaded, installing directly") Log.i(TAG, "${download.packageName} already downloaded, installing directly")
runCatching { appInstaller.getPreferredInstaller().install(existing) } runCatching {
.onFailure { Log.e(TAG, "Failed to install ${download.packageName}", it) } appInstaller.getPreferredInstaller(notifyOnFallback = true).install(existing)
}.onFailure { Log.e(TAG, "Failed to install ${download.packageName}", it) }
return return
} }
if (existing.isActive) { if (existing.isActive) {

View File

@@ -25,13 +25,17 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import com.aurora.Constants.PACKAGE_NAME_GMS import com.aurora.Constants.PACKAGE_NAME_GMS
import com.aurora.extensions.TAG
import com.aurora.extensions.getUpdateOwnerPackageNameCompat import com.aurora.extensions.getUpdateOwnerPackageNameCompat
import com.aurora.extensions.isOAndAbove import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isPAndAbove import com.aurora.extensions.isPAndAbove
import com.aurora.extensions.isSAndAbove import com.aurora.extensions.isSAndAbove
import com.aurora.extensions.toast
import com.aurora.store.BuildConfig 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.ShizukuInstaller.Companion.SHIZUKU_PACKAGE_NAME
import com.aurora.store.data.installer.base.IInstaller import com.aurora.store.data.installer.base.IInstaller
import com.aurora.store.data.model.Installer import com.aurora.store.data.model.Installer
@@ -172,8 +176,15 @@ class AppInstaller @Inject constructor(
fun hasShizukuOrSui(context: Context): Boolean = isOAndAbove && fun hasShizukuOrSui(context: Context): Boolean = isOAndAbove &&
(PackageUtil.isInstalled(context, SHIZUKU_PACKAGE_NAME) || Sui.isSui()) (PackageUtil.isInstalled(context, SHIZUKU_PACKAGE_NAME) || Sui.isSui())
fun hasShizukuPerm(): Boolean = // Shizuku.checkSelfPermission() throws when the binder is not alive (Shizuku
Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED // 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) { fun uninstall(context: Context, packageName: String) {
val intent = Intent().apply { val intent = Intent().apply {
@@ -196,31 +207,47 @@ class AppInstaller @Inject constructor(
fun getMicroGInstaller(): IInstaller = microGInstaller 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)) { Installer.SERVICE -> if (hasAuroraService(context)) {
serviceInstaller serviceInstaller
} else { } else {
defaultInstaller 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 if (selected != Installer.SESSION && installer === defaultInstaller) {
Log.i(TAG, "$selected installer unavailable, falling back to session installer")
Installer.SHIZUKU -> if (hasShizukuOrSui(context) && hasShizukuPerm()) { if (notifyOnFallback) context.toast(R.string.installer_fallback_session)
shizukuInstaller
} else {
defaultInstaller
} }
Installer.MICROG -> if (hasMicroGInstaller(context)) { return installer
microGInstaller
} else {
defaultInstaller
}
} }
} }

View File

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

View File

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

View File

@@ -178,6 +178,7 @@
<string name="installer_am_unavailable">Install App Manager or change the installer.</string> <string name="installer_am_unavailable">Install App Manager or change the installer.</string>
<string name="installer_root_unavailable">No root access. Grant it or change the installer.</string> <string name="installer_root_unavailable">No root access. Grant it or change the installer.</string>
<string name="installer_shizuku_unavailable">Shizuku is not installed or set up properly.</string> <string name="installer_shizuku_unavailable">Shizuku is not installed or set up properly.</string>
<string name="installer_fallback_session">Chosen installer is unavailable, using the default installer instead.</string>
<string name="installer_service_available">"Aurora Services is available and ready to install."</string> <string name="installer_service_available">"Aurora Services is available and ready to install."</string>
<string name="installer_service_unavailable">Install Aurora Services 1.0.9 or above, or change the installer.</string> <string name="installer_service_unavailable">Install Aurora Services 1.0.9 or above, or change the installer.</string>
<string name="installer_service_misconfigured">Set up Aurora Services and grant all permissions first.</string> <string name="installer_service_misconfigured">Set up Aurora Services and grant all permissions first.</string>