DO NOT MERGE: AppInstaller: Rework and extend silent install logic check

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-10-07 18:36:58 +05:30
parent c8c5ff79bf
commit b7cc804eeb
10 changed files with 119 additions and 38 deletions

View File

@@ -1,6 +1,9 @@
package com.aurora.extensions
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.RequiresApi
import com.aurora.store.BuildConfig
fun PackageManager.getInstallerPackageNameCompat(packageName: String): String? {
return if (isRAndAbove()) {
@@ -10,3 +13,17 @@ fun PackageManager.getInstallerPackageNameCompat(packageName: String): String? {
return getInstallerPackageName(packageName)
}
}
@RequiresApi(Build.VERSION_CODES.S)
fun PackageManager.getUpdateOwnerPackageNameCompat(packageName: String): String? {
// https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int)
val installSourceInfo = getInstallSourceInfo(packageName)
return when {
isUAndAbove() -> {
// If update ownership is null, we can still silently update it if we installed it
installSourceInfo.updateOwnerPackageName ?: installSourceInfo.installingPackageName
}
isSAndAbove() -> installSourceInfo.installingPackageName
else -> if (packageName == BuildConfig.APPLICATION_ID) BuildConfig.APPLICATION_ID else null
}
}

View File

@@ -30,10 +30,17 @@ import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.extensions.getUpdateOwnerPackageNameCompat
import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isPAndAbove
import com.aurora.extensions.isSAndAbove
import com.aurora.extensions.isTAndAbove
import com.aurora.extensions.isUAndAbove
import com.aurora.extensions.isVAndAbove
import com.aurora.store.BuildConfig
import com.aurora.store.R
import com.aurora.store.data.model.InstallerInfo
import com.aurora.store.util.CertUtil
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
@@ -101,6 +108,37 @@ class AppInstaller @Inject constructor(
return installers
}
/**
* Checks if the given package can be silently installed
* @param context [Context]
* @param packageName Package to silently install
*/
fun canInstallSilently(context: Context, packageName: String, targetSdk: Int): Boolean {
return when (getCurrentInstaller(context)) {
Installer.SESSION -> {
// Silent install cannot be done on initial install and below A12
if (!PackageUtil.isInstalled(context, packageName) || !isSAndAbove()) return false
// We cannot do silent updates if we are not the update owner
if (context.packageManager.getUpdateOwnerPackageNameCompat(packageName) != BuildConfig.APPLICATION_ID) return false
// Ensure app being installed satisfies Android's requirement for targetSdk level
when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.VANILLA_ICE_CREAM -> targetSdk == Build.VERSION_CODES.TIRAMISU
Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> targetSdk == Build.VERSION_CODES.S
Build.VERSION_CODES.TIRAMISU -> targetSdk == Build.VERSION_CODES.R
Build.VERSION_CODES.S -> targetSdk == Build.VERSION_CODES.Q
else -> false // Only Android version above 12 can silently update apps
}
}
Installer.NATIVE -> false // Deprecated
Installer.ROOT -> hasRootAccess()
Installer.SERVICE -> false // Deprecated
Installer.AM -> false // We cannot check if AppManager has ability to auto-update
Installer.SHIZUKU -> isOAndAbove() && hasShizukuOrSui(context) && hasShizukuPerm()
}
}
fun notifyInstallation(context: Context, displayName: String, packageName: String) {
val notificationManager = context.getSystemService<NotificationManager>()
val notification = NotificationUtil.getInstallNotification(context, displayName, packageName)

View File

@@ -13,7 +13,7 @@ import com.aurora.store.data.room.update.UpdateDao
@Database(
entities = [Download::class, Favourite::class, Update::class],
version = 3,
version = 4,
exportSchema = false
)
@TypeConverters(DownloadConverter::class)

View File

@@ -0,0 +1,32 @@
package com.aurora.store.data.room
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
/**
* A helper class for doing migrations for the [AuroraDatabase].
* @see [RoomModule]
*/
object MigrationHelper {
// ADD ALL NEW MIGRATION STEPS HERE TOO
val MIGRATION_1_4 = object : Migration(1, 4) {
override fun migrate(db: SupportSQLiteDatabase) {
migrateFrom3To4(db)
}
}
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(db: SupportSQLiteDatabase) = migrateFrom3To4(db)
}
/**
* Add targetSdk column to download and update table for checking if silent install is possible.
*/
private fun migrateFrom3To4(db: SupportSQLiteDatabase) {
db.apply {
execSQL("ALTER TABLE download ADD COLUMN targetSdk INTEGER")
execSQL("ALTER TABLE update ADD COLUMN targetSdk INTEGER")
}
}
}

View File

@@ -2,6 +2,8 @@ package com.aurora.store.data.room
import android.content.Context
import androidx.room.Room
import com.aurora.store.data.room.MigrationHelper.MIGRATION_1_4
import com.aurora.store.data.room.MigrationHelper.MIGRATION_3_4
import com.aurora.store.data.room.download.DownloadConverter
import com.aurora.store.data.room.download.DownloadDao
import com.aurora.store.data.room.favourites.FavouriteDao
@@ -26,6 +28,7 @@ object RoomModule {
downloadConverter: DownloadConverter
): AuroraDatabase {
return Room.databaseBuilder(context, AuroraDatabase::class.java, DATABASE)
.addMigrations(MIGRATION_3_4, MIGRATION_1_4)
.addTypeConverter(downloadConverter)
.build()
}

View File

@@ -27,7 +27,8 @@ data class Download(
var totalFiles: Int,
var downloadedFiles: Int,
var fileList: List<File>,
val sharedLibs: List<SharedLib>
val sharedLibs: List<SharedLib>,
val targetSdk: Int,
) : Parcelable {
val isFinished get() = downloadStatus in DownloadStatus.finished
val isRunning get() = downloadStatus in DownloadStatus.running
@@ -50,7 +51,8 @@ data class Download(
0,
0,
app.fileList.filterNot { it.url.isBlank() },
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) },
app.targetSdk
)
}
@@ -71,7 +73,8 @@ data class Download(
0,
0,
update.fileList,
update.sharedLibs
update.sharedLibs,
update.targetSdk
)
}
}

View File

@@ -29,7 +29,8 @@ data class Update(
val hasValidCert: Boolean,
val offerType: Int,
var fileList: List<File>,
val sharedLibs: List<SharedLib>
val sharedLibs: List<SharedLib>,
val targetSdk: Int
) : Parcelable {
companion object {
@@ -52,7 +53,8 @@ data class Update(
},
app.offerType,
app.fileList.filterNot { it.url.isBlank() },
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) },
app.targetSdk
)
}
}

View File

@@ -15,13 +15,9 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.aurora.extensions.isIgnoringBatteryOptimizations
import com.aurora.extensions.isMAndAbove
import com.aurora.extensions.isOAndAbove
import com.aurora.extensions.isSAndAbove
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.installer.AppInstaller.Companion.Installer
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.AppUtil
import com.aurora.store.util.CertUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.Preferences
@@ -141,7 +137,9 @@ class UpdateWorker @AssistedInject constructor(
} else {
if (appContext.isIgnoringBatteryOptimizations()) {
// Trigger download for apps if they can be auto-updated (if any)
updatesList.filter { canAutoUpdate(it.packageName) }.let { list ->
updatesList.filter {
AppInstaller.canInstallSilently(appContext, it.packageName, it.targetSdk)
}.let { list ->
if (list.isEmpty()) return@let
Log.i(TAG, "Found auto-update enabled apps, updating!")
@@ -149,7 +147,9 @@ class UpdateWorker @AssistedInject constructor(
}
// Notify about remaining apps (if any)
updatesList.filterNot { canAutoUpdate(it.packageName) }.let { list ->
updatesList.filterNot {
AppInstaller.canInstallSilently(appContext, it.packageName, it.targetSdk)
}.let { list ->
if (list.isEmpty()) return@let
Log.i(TAG, "Found apps that cannot be auto-updated, notifying!")
@@ -179,19 +179,4 @@ class UpdateWorker @AssistedInject constructor(
}
return Result.success()
}
/**
* Checks if the given package can be auto-updated or not
*/
private fun canAutoUpdate(packageName: String): Boolean {
return when (AppInstaller.getCurrentInstaller(appContext)) {
Installer.SESSION -> isSAndAbove() && CertUtil.isAuroraStoreApp(appContext, packageName)
Installer.NATIVE -> false
Installer.ROOT -> AppInstaller.hasRootAccess()
Installer.SERVICE -> AppInstaller.hasAuroraService(appContext)
Installer.AM -> false // We cannot check if AppManager has ability to auto-update
Installer.SHIZUKU -> isOAndAbove() && AppInstaller.hasShizukuOrSui(appContext) &&
AppInstaller.hasShizukuPerm()
}
}
}

View File

@@ -43,10 +43,6 @@ object CertUtil {
private const val CERT_BEGIN = "-----BEGIN CERTIFICATE-----"
private const val CERT_END = "-----END CERTIFICATE-----"
fun isAuroraStoreApp(context: Context, packageName: String): Boolean {
return context.packageManager.getInstallerPackageNameCompat(packageName) == context.packageName
}
fun isFDroidApp(context: Context, packageName: String): Boolean {
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
}

View File

@@ -20,6 +20,7 @@ import com.aurora.Constants
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.update.Update
@@ -109,13 +110,17 @@ object NotificationUtil {
builder.setAutoCancel(true)
builder.setCategory(Notification.CATEGORY_STATUS)
builder.setContentIntent(getContentIntentForDetails(context, download.packageName))
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_install,
context.getString(R.string.action_install),
getInstallIntent(context, download)
).build()
)
// Show install action if app cannot be silently installed
if (!AppInstaller.canInstallSilently(context, download.packageName, download.targetSdk)) {
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_install,
context.getString(R.string.action_install),
getInstallIntent(context, download)
).build()
)
}
}
DownloadStatus.DOWNLOADING, DownloadStatus.QUEUED -> {