Rework updates check & install setup

Merge updates checking logic into UpdateWorker and move trigger
methods to UpdateHelper class, similar to how DownloadHelper works.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-10-19 16:16:00 +05:30
parent 2e98e33847
commit 0eb58a4e9e
16 changed files with 348 additions and 277 deletions

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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<Update> {
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<UpdateWorker>()
.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<String, PackageInfo>? = null
): List<App> {
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<UpdateWorker>(
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
}
}
}

View File

@@ -0,0 +1,7 @@
package com.aurora.store.data.model
enum class BuildType {
RELEASE,
NIGHTLY,
DEBUG
}

View File

@@ -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
)

View File

@@ -0,0 +1,7 @@
package com.aurora.store.data.model
enum class UpdateMode {
DISABLED,
CHECK_ONLY,
CHECK_AND_INSTALL
}

View File

@@ -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 {

View File

@@ -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<UpdateWorker>(
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<NotificationManager>()
// 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<Update> {
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<Update>) {
with(appContext.getSystemService<NotificationManager>()!!) {
notify(
notificationID,
NotificationUtil.getUpdateNotification(appContext, updates)
)
}
return Result.success()
}
}

View File

@@ -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<Update>): 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()
}

View File

@@ -105,7 +105,7 @@ class DownloadFragment : BaseFragment<FragmentDownloadBinding>() {
.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)

View File

@@ -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<FragmentOnboardingBinding>() {
@Inject
lateinit var updateHelper: UpdateHelper
private var lastPosition = 0
internal class PagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
@@ -190,6 +194,6 @@ class OnboardingFragment : BaseFragment<FragmentOnboardingBinding>() {
else -> 0 // Disable
}
save(PREFERENCE_UPDATES_AUTO, updateMode)
UpdateWorker.scheduleAutomatedCheck(requireContext())
updateHelper.scheduleAutomatedCheck()
}
}

View File

@@ -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<SeekBarPreference>(PREFERENCE_UPDATES_CHECK_INTERVAL)
?.setOnPreferenceChangeListener { _, _ ->
UpdateWorker.updateAutomatedCheck(requireContext())
updateHelper.updateAutomatedCheck()
true
}
}

View File

@@ -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<SheetDozeWarningBinding>() {
@Inject
lateinit var updateHelper: UpdateHelper
private val args: DozeWarningSheetArgs by navArgs()
private lateinit var permissionProvider: PermissionProvider
@@ -26,7 +32,7 @@ class DozeWarningSheet : BaseDialogSheet<SheetDozeWarningBinding>() {
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()

View File

@@ -169,7 +169,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
.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)

View File

@@ -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)
}

View File

@@ -454,6 +454,7 @@
<!-- UpdatesFragment -->
<string name="check_updates">Check for updates</string>
<string name="checking_updates">Checking for updates</string>
<!-- ForceRestartDialog -->
<string name="force_restart_title">Restart Aurora Store</string>