AppUtil: Move installed and updatable app logic to separate class

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-03-19 18:36:46 +05:30
parent e6e00fd844
commit 13a88afa09
5 changed files with 160 additions and 173 deletions

View File

@@ -0,0 +1,20 @@
package com.aurora.extensions
import android.content.Context
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AuthValidator
import com.aurora.store.data.network.HttpClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun AuthData.isValid(context: Context): Boolean {
return withContext(Dispatchers.IO) {
try {
AuthValidator(this@isValid)
.using(HttpClient.getPreferredClient(context))
.isValid()
} catch (e: Exception) {
false
}
}
}

View File

@@ -3,7 +3,6 @@ package com.aurora.store.data.work
import android.app.NotificationManager
import android.content.Context
import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import androidx.hilt.work.HiltWorker
import androidx.work.Constraints
import androidx.work.CoroutineWorker
@@ -14,22 +13,13 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.aurora.Constants
import com.aurora.extensions.isIgnoringBatteryOptimizations
import com.aurora.extensions.isMAndAbove
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.AuthValidator
import com.aurora.store.BuildConfig
import com.aurora.store.data.model.SelfUpdate
import com.aurora.store.data.network.HttpClient
import com.aurora.extensions.isValid
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.CertUtil
import com.aurora.store.util.AppUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
@@ -53,7 +43,6 @@ class UpdateWorker @AssistedInject constructor(
companion object {
private const val TAG = "UpdateWorker"
private const val UPDATE_WORKER = "UPDATE_WORKER"
private const val RELEASE = "release"
fun cancelAutomatedCheck(context: Context) {
Log.i(TAG, "Cancelling periodic app updates!")
@@ -71,42 +60,6 @@ class UpdateWorker @AssistedInject constructor(
WorkManager.getInstance(context).updateWork(buildUpdateWork(context))
}
suspend fun getSelfUpdate(context: Context, gson: Gson): App? {
return withContext(Dispatchers.IO) {
@Suppress("KotlinConstantConditions") // False-positive for build type always not being release
if (BuildConfig.BUILD_TYPE != RELEASE) {
Log.i(TAG, "Self-updates are not available for this build!")
return@withContext null
}
try {
val response = HttpClient.getPreferredClient(context)
.get(Constants.UPDATE_URL, mapOf())
val selfUpdate =
gson.fromJson(String(response.responseBytes), SelfUpdate::class.java)
if (selfUpdate.versionCode > BuildConfig.VERSION_CODE) {
if (CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)) {
if (selfUpdate.fdroidBuild.isNotEmpty()) {
return@withContext SelfUpdate.toApp(selfUpdate, context)
}
} else if (selfUpdate.auroraBuild.isNotEmpty()) {
return@withContext SelfUpdate.toApp(selfUpdate, context)
} else {
Log.e(TAG, "Update file is missing!")
return@withContext null
}
}
} catch (exception: Exception) {
Log.e(TAG, "Failed to check self-updates", exception)
return@withContext null
}
Log.i(TAG, "No self-updates found!")
return@withContext null
}
}
private fun buildUpdateWork(context: Context): PeriodicWorkRequest {
val updateCheckInterval = Preferences.getInteger(
context,
@@ -129,105 +82,45 @@ class UpdateWorker @AssistedInject constructor(
}
}
private val updatesList = mutableListOf<App>()
private val notificationID = 100
private val workerID = 101
private val gapps: MutableSet<String> = hashSetOf(
"com.chrome.beta",
"com.chrome.canary",
"com.chrome.dev",
"com.android.chrome",
"com.niksoftware.snapseed",
"com.google.toontastic"
)
private val authData = AuthProvider.with(appContext)
.getAuthData()
private val blackList = BlacklistProvider.with(appContext)
.getBlackList()
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(workerID, NotificationUtil.getOngoingUpdateNotification(appContext))
}
override suspend fun doWork(): Result {
Log.i(TAG, "Checking for app updates")
val autoUpdatesMode = Preferences.getInteger(appContext, PREFERENCE_UPDATES_AUTO, 3)
val notifyManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notifyManager =
appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Exit if auto-updates is turned off in settings
if (autoUpdatesMode == 0) {
Log.i(TAG,"Auto-updates is disabled, bailing out!")
Log.i(TAG, "Auto-updates is disabled, bailing out!")
return Result.failure()
}
// Check for Aurora Store updates first
if (!CertUtil.isFDroidApp(appContext, BuildConfig.APPLICATION_ID)) {
getSelfUpdate(appContext, gson)?.let { updatesList.add(it) }
}
withContext(Dispatchers.IO) {
if (!isValid(authData)) {
Log.i(TAG,"AuthData is not valid, retrying later!")
if (!AuthProvider.with(appContext).getAuthData().isValid(appContext)) {
Log.i(TAG, "AuthData is not valid, retrying later!")
return@withContext Result.retry()
}
Log.i(TAG,"Checking for app updates")
val appDetailsHelper = AppDetailsHelper(authData)
.using(HttpClient.getPreferredClient(appContext))
val isGoogleFilterEnabled = Preferences.getBoolean(
appContext,
Preferences.PREFERENCE_FILTER_GOOGLE
)
val packageInfoMap = PackageUtil.getPackageInfoMap(appContext)
packageInfoMap.keys.let { packages ->
/*Filter black list*/
var filtersPackages = packages.filter { !blackList.contains(it) }
/*Filter google apps*/
if (isGoogleFilterEnabled) {
filtersPackages = filtersPackages
.filter { !it.startsWith("com.google") }
.filter { !gapps.contains(it) }
}
val appList = appDetailsHelper.getAppByPackageName(filtersPackages)
.filter { it.displayName.isNotEmpty() }
val appUpdatesList = appList.filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
} else {
false
}
}.filter { app ->
app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(
appContext,
app.packageName
)
}
}
updatesList.addAll(appUpdatesList)
try {
val updatesList = AppUtil.getUpdatableApps(appContext, gson, true)
if (updatesList.isNotEmpty()) {
if (autoUpdatesMode == 1) {
Log.i(TAG,"Found updates, notifying!")
Log.i(TAG, "Found updates, notifying!")
notifyManager.notify(
notificationID,
NotificationUtil.getUpdateNotification(appContext, updatesList)
)
} else {
if (appContext.isIgnoringBatteryOptimizations()) {
Log.i(TAG,"Found updates, updating!")
Log.i(TAG, "Found updates, updating!")
updatesList.forEach { downloadWorkerUtil.enqueueApp(it) }
} else {
// Fallback to notification if battery optimizations are enabled
Log.i(TAG,"Found updates, but battery optimizations are enabled!")
Log.i(TAG, "Found updates, but battery optimizations are enabled!")
notifyManager.notify(
notificationID,
NotificationUtil.getUpdateNotification(appContext, updatesList)
@@ -235,23 +128,19 @@ class UpdateWorker @AssistedInject constructor(
appContext.save(PREFERENCE_UPDATES_AUTO, 1)
}
}
return@withContext Result.success()
} else {
Log.i(TAG, "No updates found!")
}
Log.i(TAG,"No updates found!")
return@withContext Result.success()
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch updates", exception)
return@withContext Result.failure()
}
}
return Result.success()
}
private fun isValid(authData: AuthData): Boolean {
return try {
AuthValidator(authData)
.using(HttpClient.getPreferredClient(appContext))
.isValid()
} catch (e: Exception) {
false
}
override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(workerID, NotificationUtil.getOngoingUpdateNotification(appContext))
}
}

View File

@@ -0,0 +1,109 @@
package com.aurora.store.util
import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.store.BuildConfig
import com.aurora.store.data.model.SelfUpdate
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.providers.BlacklistProvider
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object AppUtil {
private const val TAG = "AppUtil"
private const val RELEASE = "release"
suspend fun getUpdatableApps(context: Context, gson: Gson, verifyCert: Boolean): List<App> {
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
val appUpdatesList = getFilteredInstalledApps(context, packageInfoMap).filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
} else {
false
}
}.toMutableList()
val verifiedUpdatesList = if (verifyCert) {
appUpdatesList.filter { app ->
app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(
context, app.packageName
)
}
}.toMutableList()
} else {
appUpdatesList
}
if (!CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)) {
getSelfUpdate(context, gson)?.let { verifiedUpdatesList.add(it) }
}
return verifiedUpdatesList
}
suspend fun getFilteredInstalledApps(
context: Context,
packageInfoMap: MutableMap<String, PackageInfo>? = null
): List<App> {
return withContext(Dispatchers.IO) {
val authData = AuthProvider.with(context).getAuthData()
val blackList = BlacklistProvider.with(context).getBlackList()
val appDetailsHelper =
AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
(packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages ->
val filtersPackages = packages.filter { !blackList.contains(it) }
appDetailsHelper.getAppByPackageName(filtersPackages)
.filter { it.displayName.isNotEmpty() }
.map { it.isInstalled = true; it }
}
}
}
private suspend fun getSelfUpdate(context: Context, gson: Gson): App? {
return withContext(Dispatchers.IO) {
@Suppress("KotlinConstantConditions") // False-positive for build type always not being release
if (BuildConfig.BUILD_TYPE != RELEASE) {
Log.i(TAG, "Self-updates are not available for this build!")
return@withContext null
}
try {
val response =
HttpClient.getPreferredClient(context).get(Constants.UPDATE_URL, mapOf())
val selfUpdate =
gson.fromJson(String(response.responseBytes), SelfUpdate::class.java)
if (selfUpdate.versionCode > BuildConfig.VERSION_CODE) {
if (CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID)) {
if (selfUpdate.fdroidBuild.isNotEmpty()) {
return@withContext SelfUpdate.toApp(selfUpdate, context)
}
} else if (selfUpdate.auroraBuild.isNotEmpty()) {
return@withContext SelfUpdate.toApp(selfUpdate, context)
} else {
Log.e(TAG, "Update file is missing!")
return@withContext null
}
}
} catch (exception: Exception) {
Log.e(TAG, "Failed to check self-updates", exception)
return@withContext null
}
Log.i(TAG, "No self-updates found!")
return@withContext null
}
}
}

View File

@@ -24,6 +24,7 @@ import androidx.lifecycle.viewModelScope
import com.aurora.extensions.flushAndAdd
import com.aurora.store.data.RequestState
import com.aurora.store.data.event.BusEvent
import com.aurora.store.util.AppUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
@@ -35,14 +36,13 @@ class InstalledViewModel(application: Application) : BaseAppsViewModel(applicati
init {
EventBus.getDefault().register(this)
requestState = RequestState.Init
}
override fun observe() {
viewModelScope.launch(Dispatchers.IO) {
requestState = try {
appList.flushAndAdd(getFilteredApps())
appList = AppUtil.getFilteredInstalledApps(getApplication()).toMutableList()
liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
RequestState.Complete
} catch (e: Exception) {

View File

@@ -21,12 +21,9 @@ package com.aurora.store.viewmodel.all
import android.app.Application
import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.BuildConfig
import com.aurora.store.data.work.UpdateWorker
import com.aurora.store.util.CertUtil
import com.aurora.store.util.AppUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.Preferences
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -54,42 +51,14 @@ class UpdatesViewModel @Inject constructor(
override fun observe() {
viewModelScope.launch(Dispatchers.IO) {
val isExtendedUpdateEnabled = Preferences.getBoolean(
getApplication(),
Preferences.PREFERENCE_UPDATES_EXTENDED
)
try {
getFilteredApps().filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
} else {
false
}
}.sortedBy { it.displayName.lowercase(Locale.getDefault()) }.also { apps ->
val nApps = apps.toMutableList()
if (!CertUtil.isFDroidApp(getApplication(), BuildConfig.APPLICATION_ID)) {
UpdateWorker.getSelfUpdate(getApplication(), gson)?.let { nApps.add(it) }
}
if (!isExtendedUpdateEnabled) {
nApps.addAll(
apps.filter { app ->
app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(
getApplication(),
app.packageName
)
}
}
)
_updates.emit(nApps)
} else {
_updates.emit(nApps)
}
}
val isExtendedUpdateEnabled = Preferences.getBoolean(
getApplication(), Preferences.PREFERENCE_UPDATES_EXTENDED
)
val updates =
AppUtil.getUpdatableApps(getApplication(), gson, !isExtendedUpdateEnabled)
.sortedBy { it.displayName.lowercase(Locale.getDefault()) }
_updates.emit(updates)
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
}