AppInstaller: Rework and extend silent install logic check
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
package com.aurora.extensions
|
package com.aurora.extensions
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.aurora.store.BuildConfig
|
||||||
|
|
||||||
fun PackageManager.getInstallerPackageNameCompat(packageName: String): String? {
|
fun PackageManager.getInstallerPackageNameCompat(packageName: String): String? {
|
||||||
return if (isRAndAbove()) {
|
return if (isRAndAbove()) {
|
||||||
@@ -10,3 +13,17 @@ fun PackageManager.getInstallerPackageNameCompat(packageName: String): String? {
|
|||||||
return getInstallerPackageName(packageName)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,10 +30,17 @@ import android.os.Build
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
|
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.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.R
|
||||||
import com.aurora.store.data.model.InstallerInfo
|
import com.aurora.store.data.model.InstallerInfo
|
||||||
|
import com.aurora.store.util.CertUtil
|
||||||
import com.aurora.store.util.NotificationUtil
|
import com.aurora.store.util.NotificationUtil
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
@@ -101,6 +108,37 @@ class AppInstaller @Inject constructor(
|
|||||||
return installers
|
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) {
|
fun notifyInstallation(context: Context, displayName: String, packageName: String) {
|
||||||
val notificationManager = context.getSystemService<NotificationManager>()
|
val notificationManager = context.getSystemService<NotificationManager>()
|
||||||
val notification = NotificationUtil.getInstallNotification(context, displayName, packageName)
|
val notification = NotificationUtil.getInstallNotification(context, displayName, packageName)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import com.aurora.store.data.room.update.UpdateDao
|
|||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Download::class, Favourite::class, Update::class],
|
entities = [Download::class, Favourite::class, Update::class],
|
||||||
version = 3,
|
version = 4,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
@TypeConverters(DownloadConverter::class)
|
@TypeConverters(DownloadConverter::class)
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.aurora.store.data.room
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val TAG = "MigrationHelper"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add targetSdk column to download and update table for checking if silent install is possible.
|
||||||
|
*/
|
||||||
|
private fun migrateFrom3To4(database: SupportSQLiteDatabase) {
|
||||||
|
database.beginTransaction()
|
||||||
|
try {
|
||||||
|
listOf("download", "update").forEach {
|
||||||
|
database.execSQL("ALTER TABLE `$it` ADD COLUMN targetSdk INTEGER NOT NULL")
|
||||||
|
database.execSQL("UPDATE `$it` SET targetSdk=1")
|
||||||
|
}
|
||||||
|
database.setTransactionSuccessful()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.e(TAG, "Failed while migrating from database version 3 to 4", exception)
|
||||||
|
} finally {
|
||||||
|
database.endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.aurora.store.data.room
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.Room
|
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.DownloadConverter
|
||||||
import com.aurora.store.data.room.download.DownloadDao
|
import com.aurora.store.data.room.download.DownloadDao
|
||||||
import com.aurora.store.data.room.favourites.FavouriteDao
|
import com.aurora.store.data.room.favourites.FavouriteDao
|
||||||
@@ -26,6 +28,7 @@ object RoomModule {
|
|||||||
downloadConverter: DownloadConverter
|
downloadConverter: DownloadConverter
|
||||||
): AuroraDatabase {
|
): AuroraDatabase {
|
||||||
return Room.databaseBuilder(context, AuroraDatabase::class.java, DATABASE)
|
return Room.databaseBuilder(context, AuroraDatabase::class.java, DATABASE)
|
||||||
|
.addMigrations(MIGRATION_3_4, MIGRATION_1_4)
|
||||||
.addTypeConverter(downloadConverter)
|
.addTypeConverter(downloadConverter)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ data class Download(
|
|||||||
var totalFiles: Int,
|
var totalFiles: Int,
|
||||||
var downloadedFiles: Int,
|
var downloadedFiles: Int,
|
||||||
var fileList: List<File>,
|
var fileList: List<File>,
|
||||||
val sharedLibs: List<SharedLib>
|
val sharedLibs: List<SharedLib>,
|
||||||
|
val targetSdk: Int,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
val isFinished get() = downloadStatus in DownloadStatus.finished
|
val isFinished get() = downloadStatus in DownloadStatus.finished
|
||||||
val isRunning get() = downloadStatus in DownloadStatus.running
|
val isRunning get() = downloadStatus in DownloadStatus.running
|
||||||
@@ -50,7 +51,8 @@ data class Download(
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
app.fileList.filterNot { it.url.isBlank() },
|
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,
|
||||||
0,
|
0,
|
||||||
update.fileList,
|
update.fileList,
|
||||||
update.sharedLibs
|
update.sharedLibs,
|
||||||
|
update.targetSdk
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ data class Update(
|
|||||||
val hasValidCert: Boolean,
|
val hasValidCert: Boolean,
|
||||||
val offerType: Int,
|
val offerType: Int,
|
||||||
var fileList: List<File>,
|
var fileList: List<File>,
|
||||||
val sharedLibs: List<SharedLib>
|
val sharedLibs: List<SharedLib>,
|
||||||
|
val targetSdk: Int
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -52,7 +53,8 @@ data class Update(
|
|||||||
},
|
},
|
||||||
app.offerType,
|
app.offerType,
|
||||||
app.fileList.filterNot { it.url.isBlank() },
|
app.fileList.filterNot { it.url.isBlank() },
|
||||||
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
|
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) },
|
||||||
|
app.targetSdk
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,9 @@ import androidx.work.WorkManager
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
import com.aurora.extensions.isIgnoringBatteryOptimizations
|
||||||
import com.aurora.extensions.isMAndAbove
|
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
|
||||||
import com.aurora.store.data.installer.AppInstaller.Companion.Installer
|
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import com.aurora.store.util.AppUtil
|
import com.aurora.store.util.AppUtil
|
||||||
import com.aurora.store.util.CertUtil
|
|
||||||
import com.aurora.store.util.DownloadWorkerUtil
|
import com.aurora.store.util.DownloadWorkerUtil
|
||||||
import com.aurora.store.util.NotificationUtil
|
import com.aurora.store.util.NotificationUtil
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
@@ -141,7 +137,9 @@ class UpdateWorker @AssistedInject constructor(
|
|||||||
} else {
|
} else {
|
||||||
if (appContext.isIgnoringBatteryOptimizations()) {
|
if (appContext.isIgnoringBatteryOptimizations()) {
|
||||||
// Trigger download for apps if they can be auto-updated (if any)
|
// 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
|
if (list.isEmpty()) return@let
|
||||||
|
|
||||||
Log.i(TAG, "Found auto-update enabled apps, updating!")
|
Log.i(TAG, "Found auto-update enabled apps, updating!")
|
||||||
@@ -149,7 +147,9 @@ class UpdateWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notify about remaining apps (if any)
|
// 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
|
if (list.isEmpty()) return@let
|
||||||
|
|
||||||
Log.i(TAG, "Found apps that cannot be auto-updated, notifying!")
|
Log.i(TAG, "Found apps that cannot be auto-updated, notifying!")
|
||||||
@@ -179,19 +179,4 @@ class UpdateWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
return Result.success()
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ object CertUtil {
|
|||||||
private const val CERT_BEGIN = "-----BEGIN CERTIFICATE-----"
|
private const val CERT_BEGIN = "-----BEGIN CERTIFICATE-----"
|
||||||
private const val CERT_END = "-----END 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 {
|
fun isFDroidApp(context: Context, packageName: String): Boolean {
|
||||||
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
|
return isInstalledByFDroid(context, packageName) || isSignedByFDroid(context, packageName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.aurora.Constants
|
|||||||
import com.aurora.store.MainActivity
|
import com.aurora.store.MainActivity
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.activity.InstallActivity
|
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.model.DownloadStatus
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
import com.aurora.store.data.room.update.Update
|
import com.aurora.store.data.room.update.Update
|
||||||
@@ -109,13 +110,17 @@ object NotificationUtil {
|
|||||||
builder.setAutoCancel(true)
|
builder.setAutoCancel(true)
|
||||||
builder.setCategory(Notification.CATEGORY_STATUS)
|
builder.setCategory(Notification.CATEGORY_STATUS)
|
||||||
builder.setContentIntent(getContentIntentForDetails(context, download.packageName))
|
builder.setContentIntent(getContentIntentForDetails(context, download.packageName))
|
||||||
builder.addAction(
|
|
||||||
NotificationCompat.Action.Builder(
|
// Show install action if app cannot be silently installed
|
||||||
R.drawable.ic_install,
|
if (!AppInstaller.canInstallSilently(context, download.packageName, download.targetSdk)) {
|
||||||
context.getString(R.string.action_install),
|
builder.addAction(
|
||||||
getInstallIntent(context, download)
|
NotificationCompat.Action.Builder(
|
||||||
).build()
|
R.drawable.ic_install,
|
||||||
)
|
context.getString(R.string.action_install),
|
||||||
|
getInstallIntent(context, download)
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadStatus.DOWNLOADING, DownloadStatus.QUEUED -> {
|
DownloadStatus.DOWNLOADING, DownloadStatus.QUEUED -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user