diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt index 97142a8dc..bcab787d1 100644 --- a/app/src/main/java/com/aurora/Constants.kt +++ b/app/src/main/java/com/aurora/Constants.kt @@ -23,8 +23,6 @@ object Constants { const val PARCEL_DOWNLOAD = "PARCEL_DOWNLOAD" - const val APP_ID = "com.aurora.store" - const val URL_TOS = "https://play.google.com/about/play-terms/" const val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/LICENSE" const val URL_DISCLAIMER = "https://gitlab.com/AuroraOSS/AuroraStore/blob/master/DISCLAIMER.md" diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt index ff246ba55..152d24a5b 100644 --- a/app/src/main/java/com/aurora/store/MainActivity.kt +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -34,8 +34,6 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.FloatingWindow import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController -import com.aurora.store.data.event.BusEvent -import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.model.NetworkStatus import com.aurora.store.data.receiver.MigrationReceiver import com.aurora.store.databinding.ActivityMainBinding @@ -149,22 +147,6 @@ class MainActivity : AppCompatActivity() { } // Updates - lifecycleScope.launch { - AuroraApp.events.installerEvent.collect { - when (it) { - is InstallerEvent.Installed -> updateHelper.deleteUpdate(it.packageName) - is InstallerEvent.Uninstalled -> updateHelper.deleteUpdate(it.packageName) - else -> {} - } - } - } - - lifecycleScope.launch { - AuroraApp.events.busEvent.collect { - if (it is BusEvent.Blacklisted) updateHelper.deleteUpdate(it.packageName) - } - } - lifecycleScope.launch { updateHelper.updates.collectLatest { list -> B.navView.getOrCreateBadge(R.id.updatesFragment).apply { diff --git a/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt b/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt index 042c1402c..102e5fae5 100644 --- a/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt +++ b/app/src/main/java/com/aurora/store/data/helper/UpdateHelper.kt @@ -1,156 +1,168 @@ package com.aurora.store.data.helper 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.gplayapi.network.IHttpClient +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ExistingWorkPolicy +import androidx.work.NetworkType +import androidx.work.OutOfQuotaPolicy +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.PeriodicWorkRequest +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import com.aurora.extensions.isMAndAbove import com.aurora.store.AuroraApp -import com.aurora.store.BuildConfig -import com.aurora.store.data.model.SelfUpdate -import com.aurora.store.data.providers.AuthProvider -import com.aurora.store.data.providers.BlacklistProvider -import com.aurora.store.data.room.update.Update +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.event.InstallerEvent +import com.aurora.store.data.model.UpdateMode import com.aurora.store.data.room.update.UpdateDao -import com.aurora.store.util.CertUtil -import com.aurora.store.util.PackageUtil +import com.aurora.store.data.work.UpdateWorker import com.aurora.store.util.Preferences -import com.google.gson.Gson +import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext -import javax.inject.Inject -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import java.util.concurrent.TimeUnit.HOURS +import java.util.concurrent.TimeUnit.MINUTES +import javax.inject.Inject +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +/** + * Helper class to work with the [UpdateWorker]. + */ class UpdateHelper @Inject constructor( - private val gson: Gson, - private val authProvider: AuthProvider, private val updateDao: UpdateDao, - private val blacklistProvider: BlacklistProvider, - private val httpClient: IHttpClient, @ApplicationContext private val context: Context ) { + companion object { + const val UPDATE_MODE = "UPDATE_MODE" + + private const val UPDATE_WORKER = "UPDATE_WORKER" + private const val EXPEDITED_UPDATE_WORKER = "EXPEDITED_UPDATE_WORKER" + } + private val TAG = UpdateHelper::class.java.simpleName - private val RELEASE = "release" - private val NIGHTLY = "nightly" + private val isExtendedUpdateEnabled + get() = Preferences.getBoolean(context, Preferences.PREFERENCE_UPDATES_EXTENDED) - private val isExtendedUpdateEnabled get() = Preferences.getBoolean( - context, Preferences.PREFERENCE_UPDATES_EXTENDED - ) val updates = updateDao.updates() .map { list -> if (!isExtendedUpdateEnabled) list.filter { it.hasValidCert } else list } .stateIn(AuroraApp.scope, SharingStarted.WhileSubscribed(), null) init { AuroraApp.scope.launch { - updateDao.updates().firstOrNull()?.forEach { update -> - if (!update.isInstalled(context) || update.isUpToDate(context)) { - deleteUpdate(update.packageName) - } + deleteInvalidUpdates() + }.invokeOnCompletion { + observeUpdates() + } + } + + private fun observeUpdates() { + AuroraApp.events.installerEvent.onEach { + when (it) { + is InstallerEvent.Installed -> deleteUpdate(it.packageName) + is InstallerEvent.Uninstalled -> deleteUpdate(it.packageName) + else -> {} } - } + }.launchIn(AuroraApp.scope) + + AuroraApp.events.busEvent.onEach { + if (it is BusEvent.Blacklisted) deleteUpdate(it.packageName) + }.launchIn(AuroraApp.scope) } - suspend fun checkUpdates(): List { - Log.i(TAG, "Checking for updates") - val packageInfoMap = PackageUtil.getPackageInfoMap(context) - val appUpdatesList = getFilteredInstalledApps(packageInfoMap) - .filter { - val packageInfo = packageInfoMap[it.packageName] - if (packageInfo != null) { - it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo) - } else { - false - } - }.toMutableList() + /** + * Checks for updates using an expedited worker + */ + fun checkUpdatesNow() { + val inputData = Data.Builder() + .putInt(UPDATE_MODE, UpdateMode.CHECK_ONLY.ordinal) + .build() - if (canSelfUpdate(context)) { - getSelfUpdate(context, gson)?.let { appUpdatesList.add(it) } - } + val work = OneTimeWorkRequestBuilder() + .addTag(EXPEDITED_UPDATE_WORKER) + .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST) + .setInputData(inputData) + .build() - return appUpdatesList.map { Update.fromApp(context, it) }.also { - // Cache the updates into the database - updateDao.insertUpdates(it) - } + WorkManager.getInstance(context) + .enqueueUniqueWork(EXPEDITED_UPDATE_WORKER, ExistingWorkPolicy.KEEP, work) } - suspend fun deleteUpdate(packageName: String) { + /** + * Delete update for a package from the database + * @param packageName Name of the package + */ + private suspend fun deleteUpdate(packageName: String) { updateDao.delete(packageName) } - private suspend fun getFilteredInstalledApps( - packageInfoMap: MutableMap? = null - ): List { - return withContext(Dispatchers.IO) { - val appDetailsHelper = AppDetailsHelper(authProvider.authData!!) - .using(httpClient) - - (packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages -> - val filtersPackages = packages.filter { !blacklistProvider.isBlacklisted(it) } - - appDetailsHelper.getAppByPackageName(filtersPackages) - .filter { it.displayName.isNotEmpty() } - .map { it.isInstalled = true; it } - } - } + /** + * Cancels the automated updates check + * @see [UpdateWorker] + */ + fun cancelAutomatedCheck() { + Log.i(TAG, "Cancelling periodic app updates!") + WorkManager.getInstance(context).cancelUniqueWork(UPDATE_WORKER) } - private fun canSelfUpdate(context: Context): Boolean { - return !CertUtil.isFDroidApp(context, BuildConfig.APPLICATION_ID) && - !CertUtil.isAppGalleryApp(context, BuildConfig.APPLICATION_ID) + /** + * Schedules the automated updates check + * @see [UpdateWorker] + */ + fun scheduleAutomatedCheck() { + Log.i(TAG,"Scheduling periodic app updates!") + WorkManager.getInstance(context) + .enqueueUniquePeriodicWork( + UPDATE_WORKER, + ExistingPeriodicWorkPolicy.KEEP, + getAutoUpdateWork() + ) } - private suspend fun getSelfUpdate(context: Context, gson: Gson): App? { - return withContext(Dispatchers.IO) { - @Suppress("KotlinConstantConditions") // False-positive for build type always not being release - val updateUrl = when (BuildConfig.BUILD_TYPE) { - RELEASE -> Constants.UPDATE_URL_STABLE - NIGHTLY -> Constants.UPDATE_URL_NIGHTLY - else -> { - Log.i(TAG, "Self-updates are not available for this build!") - return@withContext null - } + /** + * Updates the automated updates check to reconsider the new user preferences + * @see [UpdateWorker] + */ + fun updateAutomatedCheck() { + Log.i(TAG,"Updating periodic app updates!") + WorkManager.getInstance(context).updateWork(getAutoUpdateWork()) + } + + private fun getAutoUpdateWork(): PeriodicWorkRequest { + val updateCheckInterval = Preferences.getInteger( + context, + PREFERENCE_UPDATES_CHECK_INTERVAL, + 3 + ).toLong() + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + + if (isMAndAbove()) constraints.setRequiresDeviceIdle(true) + + return PeriodicWorkRequestBuilder( + repeatInterval = updateCheckInterval, + repeatIntervalTimeUnit = HOURS, + flexTimeInterval = 30, + flexTimeIntervalUnit = MINUTES + ).setConstraints(constraints.build()).build() + } + + private suspend fun deleteInvalidUpdates() { + updateDao.updates().firstOrNull()?.forEach { update -> + if (!update.isInstalled(context) || update.isUpToDate(context)) { + deleteUpdate(update.packageName) } - - try { - val response = httpClient.get(updateUrl, mapOf()) - val selfUpdate = - gson.fromJson(String(response.responseBytes), SelfUpdate::class.java) - - val isUpdate = when (BuildConfig.BUILD_TYPE) { - RELEASE -> selfUpdate.versionCode > BuildConfig.VERSION_CODE - NIGHTLY -> selfUpdate.timestamp > BuildConfig.TIMESTAMP - else -> false - } - - if (isUpdate) { - 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 } } } diff --git a/app/src/main/java/com/aurora/store/data/model/BuildType.kt b/app/src/main/java/com/aurora/store/data/model/BuildType.kt new file mode 100644 index 000000000..b6b7daaed --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/BuildType.kt @@ -0,0 +1,7 @@ +package com.aurora.store.data.model + +enum class BuildType { + RELEASE, + NIGHTLY, + DEBUG +} diff --git a/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt b/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt index 2d3e04ce9..455530b14 100644 --- a/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt +++ b/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt @@ -53,7 +53,7 @@ data class SelfUpdate( } return App( - packageName = Constants.APP_ID, + packageName = context.packageName, versionCode = selfUpdate.versionCode, versionName = selfUpdate.versionName, changes = selfUpdate.changelog, @@ -64,7 +64,7 @@ data class SelfUpdate( iconArtwork = Artwork(url = "$BASE_URL/$icon"), fileList = mutableListOf( File( - name = "${Constants.APP_ID}.apk", + name = "${context.packageName}.apk", url = downloadURL, size = selfUpdate.size ) diff --git a/app/src/main/java/com/aurora/store/data/model/UpdateMode.kt b/app/src/main/java/com/aurora/store/data/model/UpdateMode.kt new file mode 100644 index 000000000..e99565167 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/UpdateMode.kt @@ -0,0 +1,7 @@ +package com.aurora.store.data.model + +enum class UpdateMode { + DISABLED, + CHECK_ONLY, + CHECK_AND_INSTALL +} 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 f9c04ddcd..1f750e56d 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 @@ -59,8 +59,8 @@ data class Update( } } - fun isSelfUpdate(): Boolean { - return packageName == Constants.APP_ID + fun isSelfUpdate(context: Context): Boolean { + return packageName == context.packageName } fun isInstalled(context: Context): Boolean { 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 699fea8fe..99da93441 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 @@ -4,179 +4,220 @@ import android.app.NotificationManager import android.content.Context import android.util.Log import androidx.core.content.getSystemService +import androidx.core.content.pm.PackageInfoCompat import androidx.hilt.work.HiltWorker -import androidx.work.Constraints import androidx.work.CoroutineWorker -import androidx.work.ExistingPeriodicWorkPolicy.KEEP -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager +import androidx.work.ForegroundInfo 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.helpers.AppDetailsHelper +import com.aurora.gplayapi.network.IHttpClient +import com.aurora.store.BuildConfig import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.providers.AuthProvider -import com.aurora.store.data.helper.UpdateHelper import com.aurora.store.data.helper.DownloadHelper +import com.aurora.store.data.helper.UpdateHelper +import com.aurora.store.data.model.BuildType +import com.aurora.store.data.model.SelfUpdate +import com.aurora.store.data.model.UpdateMode +import com.aurora.store.data.providers.AccountProvider +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.data.room.update.Update +import com.aurora.store.data.room.update.UpdateDao +import com.aurora.store.util.CertUtil 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 -import com.aurora.store.util.save +import com.google.gson.Gson import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.util.concurrent.TimeUnit.HOURS -import java.util.concurrent.TimeUnit.MINUTES /** - * A periodic worker to check for updates for installed apps based on + * A worker to check for updates for installed apps based on * filters and the auto-updates mode selected by the user. The repeat interval * is configurable by the user, defaulting to 3 hours with a flex time of 30 minutes. + * + * Avoid using this worker directly and prefer using [UpdateHelper] instead. */ @HiltWorker class UpdateWorker @AssistedInject constructor( - private val updateHelper: UpdateHelper, + private val gson: Gson, + private val blacklistProvider: BlacklistProvider, + private val httpClient: IHttpClient, + private val updateDao: UpdateDao, private val downloadHelper: DownloadHelper, private val authProvider: AuthProvider, @Assisted private val appContext: Context, @Assisted workerParams: WorkerParameters ) : CoroutineWorker(appContext, workerParams) { - companion object { - private const val TAG = "UpdateWorker" - private const val UPDATE_WORKER = "UPDATE_WORKER" - - /** - * Cancels the automated updates check - * @param context Current [Context] - * @see [UpdateWorker] - */ - fun cancelAutomatedCheck(context: Context) { - Log.i(TAG, "Cancelling periodic app updates!") - WorkManager.getInstance(context).cancelUniqueWork(UPDATE_WORKER) - } - - /** - * Schedules the automated updates check - * @param context Current [Context] - * @see [UpdateWorker] - */ - fun scheduleAutomatedCheck(context: Context) { - Log.i(TAG,"Scheduling periodic app updates!") - WorkManager.getInstance(context) - .enqueueUniquePeriodicWork(UPDATE_WORKER, KEEP, buildUpdateWork(context)) - } - - /** - * Updates the automated updates check to reconsider the new user preferences - * @param context Current [Context] - * @see [UpdateWorker] - */ - fun updateAutomatedCheck(context: Context) { - Log.i(TAG,"Updating periodic app updates!") - WorkManager.getInstance(context).updateWork(buildUpdateWork(context)) - } - - private fun buildUpdateWork(context: Context): PeriodicWorkRequest { - val updateCheckInterval = Preferences.getInteger( - context, - PREFERENCE_UPDATES_CHECK_INTERVAL, - 3 - ).toLong() - - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.UNMETERED) - .setRequiresBatteryNotLow(true) - - if (isMAndAbove()) constraints.setRequiresDeviceIdle(true) - - return PeriodicWorkRequestBuilder( - repeatInterval = updateCheckInterval, - repeatIntervalTimeUnit = HOURS, - flexTimeInterval = 30, - flexTimeIntervalUnit = MINUTES - ).setConstraints(constraints.build()).build() - } - } + private val TAG = UpdateWorker::class.java.simpleName private val notificationID = 100 + @Suppress("KotlinConstantConditions") + private val buildType = when (BuildConfig.BUILD_TYPE) { + "release" -> BuildType.RELEASE + "nightly" -> BuildType.NIGHTLY + else -> BuildType.DEBUG + } + + private val canSelfUpdate = !CertUtil.isFDroidApp(appContext, BuildConfig.APPLICATION_ID) && + !CertUtil.isAppGalleryApp(appContext, BuildConfig.APPLICATION_ID) && + buildType != BuildType.DEBUG + override suspend fun doWork(): Result { Log.i(TAG, "Checking for app updates") + val updateMode = UpdateMode.entries[inputData.getInt( + UpdateHelper.UPDATE_MODE, + Preferences.getInteger(appContext, PREFERENCE_UPDATES_AUTO, 3) + )] - val autoUpdatesMode = Preferences.getInteger(appContext, PREFERENCE_UPDATES_AUTO, 3) - val notificationManager = appContext.getSystemService() - - // Exit if auto-updates is turned off in settings - if (autoUpdatesMode == 0) { - Log.i(TAG, "Auto-updates is disabled, bailing out!") + if (updateMode == UpdateMode.DISABLED || !AccountProvider.isLoggedIn(appContext)) { + Log.i(TAG, "Updates are disabled, bailing out!") return Result.failure() } - withContext(Dispatchers.IO) { - if (!authProvider.isSavedAuthDataValid()) { - Log.i(TAG, "AuthData is not valid, retrying later!") - return@withContext Result.retry() + if (!authProvider.isSavedAuthDataValid()) { + Log.i(TAG, "AuthData is not valid, retrying later!") + return Result.retry() + } + + try { + val updates = checkUpdates() + + if (updates.isEmpty()) { + Log.i(TAG, "No updates found!") + return Result.success() + } + + // Notify and exit if we are only checking for updates or if battery optimizations are enabled + if (updateMode == UpdateMode.CHECK_ONLY || !appContext.isIgnoringBatteryOptimizations()) { + Log.i(TAG, "Found updates, notifying!") + notifyUpdates(updates) + return Result.success() + } + + // Clean the update list to prepare for installing + val filteredUpdates = updates + .filter { it.hasValidCert } + .filterNot { it.isSelfUpdate(appContext) } + .partition { + AppInstaller.canInstallSilently(appContext, it.packageName, it.targetSdk) + } + + // Notify about apps that cannot be auto-updated + notifyUpdates(filteredUpdates.second) + + // Trigger download for apps if they can be auto-updated + filteredUpdates.first.forEach { downloadHelper.enqueueUpdate(it) } + + return Result.success() + } catch (exception: Exception) { + Log.e(TAG, "Failed to fetch updates", exception) + return Result.failure() + } + } + + override suspend fun getForegroundInfo(): ForegroundInfo { + return ForegroundInfo( + notificationID, + NotificationUtil.getUpdateNotification(appContext) + ) + } + + /** + * Checks and returns updates for all possible apps + */ + private suspend fun checkUpdates(): List { + return withContext(Dispatchers.IO) { + val packageInfoMap = PackageUtil.getPackageInfoMap(appContext) + val appDetailsHelper = AppDetailsHelper(authProvider.authData!!) + .using(httpClient) + + val updates = packageInfoMap.keys.let { packages -> + val filteredPackages = packages.filter { !blacklistProvider.isBlacklisted(it) } + appDetailsHelper.getAppByPackageName(filteredPackages) + .filter { it.displayName.isNotEmpty() } + .map { it.isInstalled = true; it } + }.filter { + val packageInfo = packageInfoMap[it.packageName] + if (packageInfo != null) { + it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo) + } else { + false + } + }.toMutableList() + + if (canSelfUpdate) getSelfUpdate()?.let { updates.add(it) } + + return@withContext updates.map { Update.fromApp(appContext, it) }.also { + // Cache the updates into the database + updateDao.insertUpdates(it) + } + } + } + + /** + * Checks and returns updates for Aurora Store if available + */ + private suspend fun getSelfUpdate(): App? { + return withContext(Dispatchers.IO) { + val updateUrl = when (buildType) { + BuildType.RELEASE -> Constants.UPDATE_URL_STABLE + BuildType.NIGHTLY -> Constants.UPDATE_URL_NIGHTLY + else -> { + Log.i(TAG, "Self-updates are not available for this build!") + return@withContext null + } } try { - val updatesList = updateHelper.checkUpdates() - .filter { it.hasValidCert } - .filterNot { it.isSelfUpdate() } + val response = httpClient.get(updateUrl, mapOf()) + val selfUpdate = gson.fromJson( + String(response.responseBytes), + SelfUpdate::class.java + ) - if (updatesList.isNotEmpty()) { - if (autoUpdatesMode == 1) { - Log.i(TAG, "Found updates, notifying!") - notificationManager!!.notify( - notificationID, - NotificationUtil.getUpdateNotification(appContext, updatesList) - ) - } else { - if (appContext.isIgnoringBatteryOptimizations()) { - // Trigger download for apps if they can be auto-updated (if any) - 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!") - list.forEach { downloadHelper.enqueueUpdate(it) } - } - - // Notify about remaining apps (if any) - 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!") - notificationManager!!.notify( - notificationID, - NotificationUtil.getUpdateNotification(appContext, list) - ) - } - } else { - // Fallback to notification if battery optimizations are enabled - Log.i(TAG, "Found updates, but battery optimizations are enabled!") - notificationManager!!.notify( - notificationID, - NotificationUtil.getUpdateNotification(appContext, updatesList) - ) - appContext.save(PREFERENCE_UPDATES_AUTO, 1) - } - } - } else { - Log.i(TAG, "No updates found!") + val isUpdate = when (buildType) { + BuildType.RELEASE -> selfUpdate.versionCode > BuildConfig.VERSION_CODE + BuildType.NIGHTLY -> selfUpdate.timestamp > BuildConfig.TIMESTAMP + else -> false + } + + if (isUpdate) { + if (CertUtil.isFDroidApp(appContext, BuildConfig.APPLICATION_ID)) { + if (selfUpdate.fdroidBuild.isNotEmpty()) { + return@withContext SelfUpdate.toApp(selfUpdate, appContext) + } + } else if (selfUpdate.auroraBuild.isNotEmpty()) { + return@withContext SelfUpdate.toApp(selfUpdate, appContext) + } else { + Log.e(TAG, "Update file is missing!") + return@withContext null + } } - return@withContext Result.success() } catch (exception: Exception) { - Log.e(TAG, "Failed to fetch updates", exception) - return@withContext Result.failure() + Log.e(TAG, "Failed to check self-updates", exception) + return@withContext null } + + Log.i(TAG, "No self-updates found!") + return@withContext null + } + } + + private fun notifyUpdates(updates: List) { + with(appContext.getSystemService()!!) { + notify( + notificationID, + NotificationUtil.getUpdateNotification(appContext, updates) + ) } - return Result.success() } } 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 06f781533..c0cdbd79e 100644 --- a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt +++ b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt @@ -190,6 +190,14 @@ object NotificationUtil { .build() } + fun getUpdateNotification(context: Context): Notification { + return NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_UPDATES) + .setSmallIcon(R.drawable.ic_updates) + .setContentTitle(context.getString(R.string.checking_updates)) + .setOngoing(true) + .build() + } + fun getUpdateNotification(context: Context, updatesList: List): Notification { val contentIntent = NavDeepLinkBuilder(context) .setGraph(R.navigation.mobile_navigation) @@ -261,6 +269,7 @@ object NotificationUtil { .setSmallIcon(R.drawable.ic_file_copy) .setContentTitle(context.getString(R.string.export_app_title)) .setContentText(context.getString(R.string.export_app_summary)) + .setOngoing(true) .build() } diff --git a/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadFragment.kt b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadFragment.kt index 2af049730..21119802a 100644 --- a/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadFragment.kt @@ -105,7 +105,7 @@ class DownloadFragment : BaseFragment() { .id(it.packageName) .download(it) .click { _ -> - if (it.packageName == Constants.APP_ID) { + if (it.packageName == requireContext().packageName) { requireContext().browse(GITLAB_URL) } else { openDetailsFragment(it.packageName) diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt index 8011bcc53..4d0ee0a6f 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingFragment.kt @@ -33,8 +33,8 @@ import com.aurora.Constants import com.aurora.extensions.areNotificationsEnabled import com.aurora.extensions.isIgnoringBatteryOptimizations import com.aurora.store.R +import com.aurora.store.data.helper.UpdateHelper import com.aurora.store.data.work.CacheWorker -import com.aurora.store.data.work.UpdateWorker import com.aurora.store.databinding.FragmentOnboardingBinding import com.aurora.store.util.CertUtil import com.aurora.store.util.PackageUtil @@ -59,10 +59,14 @@ import com.aurora.store.util.save import com.aurora.store.view.ui.commons.BaseFragment import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class OnboardingFragment : BaseFragment() { + @Inject + lateinit var updateHelper: UpdateHelper + private var lastPosition = 0 internal class PagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : @@ -190,6 +194,6 @@ class OnboardingFragment : BaseFragment() { else -> 0 // Disable } save(PREFERENCE_UPDATES_AUTO, updateMode) - UpdateWorker.scheduleAutomatedCheck(requireContext()) + updateHelper.scheduleAutomatedCheck() } } diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt index 56347ad0f..9c7a4870c 100644 --- a/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt @@ -28,14 +28,18 @@ import androidx.preference.SeekBarPreference import com.aurora.extensions.isIgnoringBatteryOptimizations import com.aurora.store.MobileNavigationDirections import com.aurora.store.R -import com.aurora.store.data.work.UpdateWorker +import com.aurora.store.data.helper.UpdateHelper import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class UpdatesPreference : BasePreferenceFragment() { + @Inject + lateinit var updateHelper: UpdateHelper + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences_updates, rootKey) @@ -43,11 +47,11 @@ class UpdatesPreference : BasePreferenceFragment() { ?.setOnPreferenceChangeListener { _, newValue -> val value = newValue.toString().toInt() when (value) { - 0 -> UpdateWorker.cancelAutomatedCheck(requireContext()) - 1 -> UpdateWorker.scheduleAutomatedCheck(requireContext()) + 0 -> updateHelper.cancelAutomatedCheck() + 1 -> updateHelper.scheduleAutomatedCheck() else -> { if (requireContext().isIgnoringBatteryOptimizations()) { - UpdateWorker.scheduleAutomatedCheck(requireContext()) + updateHelper.scheduleAutomatedCheck() return@setOnPreferenceChangeListener true } else { findNavController().navigate( @@ -61,7 +65,7 @@ class UpdatesPreference : BasePreferenceFragment() { findPreference(PREFERENCE_UPDATES_CHECK_INTERVAL) ?.setOnPreferenceChangeListener { _, _ -> - UpdateWorker.updateAutomatedCheck(requireContext()) + updateHelper.updateAutomatedCheck() true } } diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/DozeWarningSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/DozeWarningSheet.kt index 2b756b852..de43ab5f6 100644 --- a/app/src/main/java/com/aurora/store/view/ui/sheets/DozeWarningSheet.kt +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/DozeWarningSheet.kt @@ -7,14 +7,20 @@ import com.aurora.extensions.isIgnoringBatteryOptimizations import com.aurora.extensions.toast import com.aurora.store.PermissionType import com.aurora.store.R +import com.aurora.store.data.helper.UpdateHelper import com.aurora.store.data.providers.PermissionProvider -import com.aurora.store.data.work.UpdateWorker import com.aurora.store.databinding.SheetDozeWarningBinding import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO import com.aurora.store.util.save +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +@AndroidEntryPoint class DozeWarningSheet : BaseDialogSheet() { + @Inject + lateinit var updateHelper: UpdateHelper + private val args: DozeWarningSheetArgs by navArgs() private lateinit var permissionProvider: PermissionProvider @@ -26,7 +32,7 @@ class DozeWarningSheet : BaseDialogSheet() { if (requireContext().isIgnoringBatteryOptimizations()) { if (args.enableAutoUpdate) { requireContext().save(PREFERENCE_UPDATES_AUTO, 2) - UpdateWorker.scheduleAutomatedCheck(requireContext()) + updateHelper.scheduleAutomatedCheck() } toast(R.string.toast_permission_granted) activity?.recreate() diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt index 55c74a2fe..507f80cbf 100644 --- a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -169,7 +169,7 @@ class UpdatesFragment : BaseFragment() { .update(update) .download(download) .click { _ -> - if (update.packageName == Constants.APP_ID) { + if (update.packageName == requireContext().packageName) { requireContext().browse(Constants.GITLAB_URL) } else { openDetailsFragment(update.packageName) diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt index 6f79b00b5..d8fd9e3af 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt @@ -51,7 +51,7 @@ class UpdatesViewModel @Inject constructor( _fetchingUpdates.value = true viewModelScope.launch(Dispatchers.IO) { try { - updateHelper.checkUpdates() + updateHelper.checkUpdatesNow() } catch (exception: Exception) { Log.d(TAG, "Failed to get updates", exception) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8750035b..e90e97e15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -454,6 +454,7 @@ Check for updates + Checking for updates Restart Aurora Store