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:
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aurora.store.data.model
|
||||
|
||||
enum class BuildType {
|
||||
RELEASE,
|
||||
NIGHTLY,
|
||||
DEBUG
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aurora.store.data.model
|
||||
|
||||
enum class UpdateMode {
|
||||
DISABLED,
|
||||
CHECK_ONLY,
|
||||
CHECK_AND_INSTALL
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user