diff --git a/app/src/main/java/com/aurora/extensions/PackageManager.kt b/app/src/main/java/com/aurora/extensions/PackageManager.kt index 67c11e178..37931c4a9 100644 --- a/app/src/main/java/com/aurora/extensions/PackageManager.kt +++ b/app/src/main/java/com/aurora/extensions/PackageManager.kt @@ -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 + } +} diff --git a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt index b888a8daa..3f35fac2f 100644 --- a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt +++ b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt @@ -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() val notification = NotificationUtil.getInstallNotification(context, displayName, packageName) diff --git a/app/src/main/java/com/aurora/store/data/room/AuroraDatabase.kt b/app/src/main/java/com/aurora/store/data/room/AuroraDatabase.kt index 6b45452f5..31e4533f3 100644 --- a/app/src/main/java/com/aurora/store/data/room/AuroraDatabase.kt +++ b/app/src/main/java/com/aurora/store/data/room/AuroraDatabase.kt @@ -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) diff --git a/app/src/main/java/com/aurora/store/data/room/MigrationHelper.kt b/app/src/main/java/com/aurora/store/data/room/MigrationHelper.kt new file mode 100644 index 000000000..e9f8f213d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/room/MigrationHelper.kt @@ -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") + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/room/RoomModule.kt b/app/src/main/java/com/aurora/store/data/room/RoomModule.kt index aa0516103..049e25d81 100644 --- a/app/src/main/java/com/aurora/store/data/room/RoomModule.kt +++ b/app/src/main/java/com/aurora/store/data/room/RoomModule.kt @@ -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() } diff --git a/app/src/main/java/com/aurora/store/data/room/download/Download.kt b/app/src/main/java/com/aurora/store/data/room/download/Download.kt index 5ee316f96..27013588f 100644 --- a/app/src/main/java/com/aurora/store/data/room/download/Download.kt +++ b/app/src/main/java/com/aurora/store/data/room/download/Download.kt @@ -27,7 +27,8 @@ data class Download( var totalFiles: Int, var downloadedFiles: Int, var fileList: List, - val sharedLibs: List + val sharedLibs: List, + 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 ) } } diff --git a/app/src/main/java/com/aurora/store/data/room/update/Update.kt b/app/src/main/java/com/aurora/store/data/room/update/Update.kt index 997eaca29..1c3dd3ad9 100644 --- a/app/src/main/java/com/aurora/store/data/room/update/Update.kt +++ b/app/src/main/java/com/aurora/store/data/room/update/Update.kt @@ -29,7 +29,8 @@ data class Update( val hasValidCert: Boolean, val offerType: Int, var fileList: List, - val sharedLibs: List + val sharedLibs: List, + 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 ) } } diff --git a/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt b/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt index 4343bd991..51a2448c2 100644 --- a/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt +++ b/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt @@ -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() - } - } } diff --git a/app/src/main/java/com/aurora/store/util/CertUtil.kt b/app/src/main/java/com/aurora/store/util/CertUtil.kt index 41134bbf6..9310e97d8 100644 --- a/app/src/main/java/com/aurora/store/util/CertUtil.kt +++ b/app/src/main/java/com/aurora/store/util/CertUtil.kt @@ -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) } diff --git a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt index 6c23bb631..7987d0648 100644 --- a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt +++ b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt @@ -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 -> {