diff --git a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt
index 875997773f127f10c1e9516f0bac87947ffe37e2..373c5903e352f7b737a8a5d8741daf8e57577ec6 100644
--- a/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt
+++ b/app/src/main/java/foundation/e/apps/MainActivityViewModel.kt
@@ -478,6 +478,7 @@ class MainActivityViewModel @Inject constructor(
if (shouldShowPaidAppsSnackBar(app)) {
return
}
+
viewModelScope.launch {
val fusedDownload: FusedDownload
try {
diff --git a/app/src/main/java/foundation/e/apps/api/DownloadManager.kt b/app/src/main/java/foundation/e/apps/api/DownloadManager.kt
index 850c0acb142b9869ba312f049271fd0fe3f6359a..3d6f64dbfc2f703494c72708bc100098a7724122 100644
--- a/app/src/main/java/foundation/e/apps/api/DownloadManager.kt
+++ b/app/src/main/java/foundation/e/apps/api/DownloadManager.kt
@@ -127,11 +127,36 @@ class DownloadManager @Inject constructor(
}
}
- private fun tickerFlow(downloadId: Long, period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
+ private fun tickerFlow(
+ downloadId: Long,
+ period: Duration,
+ initialDelay: Duration = Duration.ZERO
+ ) = flow {
delay(initialDelay)
while (downloadsMaps[downloadId]!!) {
emit(Unit)
delay(period)
}
}
+
+ fun isDownloadSuccessful(downloadId: Long): Boolean {
+ return getDownloadStatus(downloadId) == DownloadManager.STATUS_SUCCESSFUL
+ }
+
+ private fun getDownloadStatus(downloadId: Long): Int {
+ try {
+ downloadManager.query(downloadManagerQuery.setFilterById(downloadId))
+ .use { cursor ->
+ if (cursor.moveToFirst()) {
+ val status =
+ cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
+ Timber.d("Download Failed: downloadId: $downloadId $status")
+ return status
+ }
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ return DownloadManager.STATUS_FAILED
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt b/app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt
index f9bd5443639eb6523efafa4ddfc1afeb754e08b7..ea7e58338ecc22ede69a76fd091d9731bcc1bfb2 100644
--- a/app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt
+++ b/app/src/main/java/foundation/e/apps/api/cleanapk/ApkSignatureManager.kt
@@ -27,6 +27,7 @@ import org.bouncycastle.openpgp.PGPUtil
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
+import timber.log.Timber
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.InputStream
@@ -35,11 +36,16 @@ import java.security.Security
object ApkSignatureManager {
fun verifyFdroidSignature(context: Context, apkFilePath: String, signature: String): Boolean {
Security.addProvider(BouncyCastleProvider())
- return verifyAPKSignature(
- BufferedInputStream(FileInputStream(apkFilePath)),
- signature.byteInputStream(Charsets.UTF_8),
- context.assets.open("f-droid.org-signing-key.gpg")
- )
+ try {
+ return verifyAPKSignature(
+ BufferedInputStream(FileInputStream(apkFilePath)),
+ signature.byteInputStream(Charsets.UTF_8),
+ context.assets.open("f-droid.org-signing-key.gpg")
+ )
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ return false
}
private fun verifyAPKSignature(
diff --git a/app/src/main/java/foundation/e/apps/api/fused/UpdatesDao.kt b/app/src/main/java/foundation/e/apps/api/fused/UpdatesDao.kt
new file mode 100644
index 0000000000000000000000000000000000000000..04d6ea2e28391b60dbe61611aadf8fdb6405f883
--- /dev/null
+++ b/app/src/main/java/foundation/e/apps/api/fused/UpdatesDao.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 MURENA SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.apps.api.fused
+
+import foundation.e.apps.api.fused.data.FusedApp
+
+object UpdatesDao {
+ var appsAwaitingForUpdate: List = listOf()
+
+ fun hasAnyAppsForUpdate() = appsAwaitingForUpdate.isNotEmpty()
+}
diff --git a/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt b/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt
index c5a949a87fc41a71944a3dbabe36ca3b6492ff36..2661920c362ed334b2c85c40cd4aa9dcbca4f46b 100644
--- a/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt
+++ b/app/src/main/java/foundation/e/apps/manager/download/DownloadManagerUtils.kt
@@ -1,88 +1,115 @@
-/*
- * Copyright ECORP SAS 2022
- * Apps Quickly and easily install Android apps onto your device!
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package foundation.e.apps.manager.download
-
-import android.content.Context
-import dagger.hilt.android.qualifiers.ApplicationContext
-import foundation.e.apps.manager.database.fusedDownload.FusedDownload
-import foundation.e.apps.manager.fused.FusedManagerRepository
-import foundation.e.apps.utils.enums.Origin
-import foundation.e.apps.utils.enums.Status
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import timber.log.Timber
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class DownloadManagerUtils @Inject constructor(
- private val fusedManagerRepository: FusedManagerRepository,
- @ApplicationContext private val context: Context
-) {
- private val TAG = DownloadManagerUtils::class.java.simpleName
- private val mutex = Mutex()
-
- @DelicateCoroutinesApi
- fun cancelDownload(downloadId: Long) {
- GlobalScope.launch {
- val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId)
- fusedManagerRepository.cancelDownload(fusedDownload)
- }
- }
-
- @DelicateCoroutinesApi
- fun updateDownloadStatus(downloadId: Long) {
- GlobalScope.launch {
- mutex.withLock {
- delay(1500) // Waiting for downloadmanager to publish the progress of last bytes
- val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId)
- if (fusedDownload.id.isNotEmpty()) {
- fusedDownload.downloadIdMap[downloadId] = true
- fusedManagerRepository.updateFusedDownload(fusedDownload)
- val downloaded = fusedDownload.downloadIdMap.values.filter { it }.size
- Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $downloaded/${fusedDownload.downloadIdMap.size}")
- if (downloaded == fusedDownload.downloadIdMap.size && checkCleanApkSignatureOK(fusedDownload)) {
- fusedManagerRepository.moveOBBFileToOBBDirectory(fusedDownload)
- fusedDownload.status = Status.DOWNLOADED
- fusedManagerRepository.updateFusedDownload(fusedDownload)
- }
- }
- }
- }
- }
-
- private suspend fun checkCleanApkSignatureOK(fusedDownload: FusedDownload): Boolean {
- if (fusedDownload.origin != Origin.CLEANAPK || fusedManagerRepository.isFdroidApplicationSigned(
- context,
- fusedDownload
- )
- ) {
- Timber.d("Apk signature is OK")
- return true
- }
- fusedDownload.status = Status.INSTALLATION_ISSUE
- fusedManagerRepository.updateFusedDownload(fusedDownload)
- Timber.d("CleanApk signature is Wrong!")
- return false
- }
-}
+/*
+ * Copyright ECORP SAS 2022
+ * Apps Quickly and easily install Android apps onto your device!
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.apps.manager.download
+
+import android.content.Context
+import dagger.hilt.android.qualifiers.ApplicationContext
+import foundation.e.apps.api.DownloadManager
+import foundation.e.apps.manager.database.fusedDownload.FusedDownload
+import foundation.e.apps.manager.fused.FusedManagerRepository
+import foundation.e.apps.utils.enums.Origin
+import foundation.e.apps.utils.enums.Status
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class DownloadManagerUtils @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val fusedManagerRepository: FusedManagerRepository,
+ private val downloadManager: DownloadManager
+) {
+ private val TAG = DownloadManagerUtils::class.java.simpleName
+ private val mutex = Mutex()
+
+ @DelicateCoroutinesApi
+ fun cancelDownload(downloadId: Long) {
+ GlobalScope.launch {
+ val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId)
+ fusedManagerRepository.cancelDownload(fusedDownload)
+ }
+ }
+
+ @DelicateCoroutinesApi
+ fun updateDownloadStatus(downloadId: Long) {
+ GlobalScope.launch {
+ mutex.withLock {
+ delay(1500) // Waiting for downloadmanager to publish the progress of last bytes
+ val fusedDownload = fusedManagerRepository.getFusedDownload(downloadId)
+ if (fusedDownload.id.isNotEmpty()) {
+ updateDownloadIdMap(fusedDownload, downloadId)
+ val numberOfDownloadedItems =
+ fusedDownload.downloadIdMap.values.filter { it }.size
+ Timber.d("===> updateDownloadStatus: ${fusedDownload.name}: $downloadId: $numberOfDownloadedItems/${fusedDownload.downloadIdMap.size}")
+
+ if (validateDownload(numberOfDownloadedItems, fusedDownload, downloadId)) {
+ Timber.d("===> Download is completed for: ${fusedDownload.name}")
+ fusedManagerRepository.moveOBBFileToOBBDirectory(fusedDownload)
+ fusedDownload.status = Status.DOWNLOADED
+ fusedManagerRepository.updateFusedDownload(fusedDownload)
+ }
+ }
+ }
+ }
+ }
+
+ private suspend fun validateDownload(
+ numberOfDownloadedItems: Int,
+ fusedDownload: FusedDownload,
+ downloadId: Long
+ ) = downloadManager.isDownloadSuccessful(downloadId) &&
+ areAllFilesDownloaded(
+ numberOfDownloadedItems,
+ fusedDownload
+ ) && checkCleanApkSignatureOK(fusedDownload)
+
+ private fun areAllFilesDownloaded(
+ numberOfDownloadedItems: Int,
+ fusedDownload: FusedDownload
+ ) = numberOfDownloadedItems == fusedDownload.downloadIdMap.size
+
+ private suspend fun updateDownloadIdMap(
+ fusedDownload: FusedDownload,
+ downloadId: Long
+ ) {
+ fusedDownload.downloadIdMap[downloadId] = true
+ fusedManagerRepository.updateFusedDownload(fusedDownload)
+ }
+
+ private suspend fun checkCleanApkSignatureOK(fusedDownload: FusedDownload): Boolean {
+ if (fusedDownload.origin != Origin.CLEANAPK || fusedManagerRepository.isFdroidApplicationSigned(
+ context,
+ fusedDownload
+ )
+ ) {
+ Timber.d("Apk signature is OK")
+ return true
+ }
+ fusedDownload.status = Status.INSTALLATION_ISSUE
+ fusedManagerRepository.updateFusedDownload(fusedDownload)
+ Timber.d("CleanApk signature is Wrong!")
+ return false
+ }
+}
diff --git a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt
index ece25c8b6fc811064746e58bad59c3f222918c15..ed576ff67a627e49376c3abde7908d7ae6f0313b 100644
--- a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt
+++ b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerImpl.kt
@@ -74,6 +74,10 @@ class FusedManagerImpl @Inject constructor(
databaseRepository.addDownload(fusedDownload)
}
+ suspend fun getDownloadById(fusedDownload: FusedDownload): FusedDownload? {
+ return databaseRepository.getDownloadById(fusedDownload.id)
+ }
+
suspend fun getDownloadList(): List {
return databaseRepository.getDownloadList()
}
diff --git a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt
index e6ffa4627e2111c65a4c3450d390a81aab1c0d14..98c24363be6927f5f2b50dcc84c2dd121aa81bcb 100644
--- a/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt
+++ b/app/src/main/java/foundation/e/apps/manager/fused/FusedManagerRepository.kt
@@ -9,6 +9,7 @@ import foundation.e.apps.api.fdroid.FdroidRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.download.data.DownloadProgress
+import foundation.e.apps.manager.workmanager.InstallWorkManager
import foundation.e.apps.utils.enums.Status
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
@@ -34,6 +35,16 @@ class FusedManagerRepository @Inject constructor(
}
suspend fun addDownload(fusedDownload: FusedDownload) {
+ if (InstallWorkManager.checkWorkIsAlreadyAvailable(fusedDownload.id)) {
+ return
+ }
+
+ val existingFusedDownload = fusedManagerImpl.getDownloadById(fusedDownload)
+ // We don't want to add any thing, if it already exists without INSTALLATION_ISSUE
+ if (existingFusedDownload != null && existingFusedDownload.status != Status.INSTALLATION_ISSUE) {
+ return
+ }
+
return fusedManagerImpl.addDownload(fusedDownload)
}
diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
index ad411ac0a4e79d1e065d45f3b5f3957df2db5e85..7d00c04b4211dc5e6475752a8a0ef1de5921853e 100644
--- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
+++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
@@ -50,6 +50,7 @@ class PkgManagerModule @Inject constructor(
companion object {
const val ERROR_PACKAGE_INSTALL = "ERROR_PACKAGE_INSTALL"
const val PACKAGE_NAME = "packageName"
+ const val FAKE_STORE_PACKAGE_NAME = "com.android.vending"
private const val TAG = "PkgManagerModule"
}
private val packageManager = context.packageManager
@@ -115,12 +116,11 @@ class PkgManagerModule @Inject constructor(
return
}
if (fusedDownload.origin == Origin.GPLAY) {
- val fakeStorePackageName = "com.android.vending"
- if (fusedDownload.type == Type.NATIVE && isInstalled(fakeStorePackageName)) {
+ if (fusedDownload.type == Type.NATIVE && isInstalled(FAKE_STORE_PACKAGE_NAME)) {
val targetPackage = fusedDownload.packageName
try {
- packageManager.setInstallerPackageName(targetPackage, fakeStorePackageName)
- Timber.d("Changed installer to $fakeStorePackageName for $targetPackage")
+ packageManager.setInstallerPackageName(targetPackage, FAKE_STORE_PACKAGE_NAME)
+ Timber.d("Changed installer to $FAKE_STORE_PACKAGE_NAME for $targetPackage")
} catch (e: Exception) {
e.printStackTrace()
}
@@ -239,6 +239,11 @@ class PkgManagerModule @Inject constructor(
return userPackages
}
+ fun isGplay(packageName: String): Boolean {
+ val installerPackageName = packageManager.getInstallerPackageName(packageName)
+ return installerPackageName?.contains(FAKE_STORE_PACKAGE_NAME) == true
+ }
+
fun getAllSystemApps(): List {
return packageManager.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY)
}
diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
index 08edd715f426ccfc23b3d99626c2298befa8a5ba..fd782aea4cee10d8763fe358aced297daf469e8a 100644
--- a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
+++ b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
@@ -6,6 +6,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
+import java.lang.Exception
object InstallWorkManager {
const val INSTALL_WORK_NAME = "APP_LOUNGE_INSTALL_APP"
@@ -27,4 +28,18 @@ object InstallWorkManager {
fun cancelWork(tag: String) {
WorkManager.getInstance(context).cancelAllWorkByTag(tag)
}
+
+ fun checkWorkIsAlreadyAvailable(tag: String): Boolean {
+ val works = WorkManager.getInstance(context).getWorkInfosByTag(tag)
+ try {
+ works.get().forEach {
+ if (it.tags.contains(tag) && !it.state.isFinished) {
+ return true
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ return false
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt
index 5e8d1a1009037984b9021cdec2827acc0a86c4cc..aae5bb2b8f99878ec0616f4b592a02d646f1518c 100644
--- a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt
+++ b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt
@@ -28,6 +28,7 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.aurora.gplayapi.data.models.AuthData
import dagger.hilt.android.AndroidEntryPoint
@@ -200,10 +201,27 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte
updatesViewModel.getUpdates(authData)
binding.button.setOnClickListener {
UpdatesWorkManager.startUpdateAllWork(requireContext().applicationContext)
+ observeUpdateWork()
binding.button.isEnabled = false
}
}
+ private fun observeUpdateWork() {
+ WorkManager.getInstance(requireContext())
+ .getWorkInfosByTagLiveData(UpdatesWorkManager.UPDATES_WORK_NAME)
+ .observe(viewLifecycleOwner) {
+ val errorStates =
+ listOf(
+ WorkInfo.State.FAILED,
+ WorkInfo.State.BLOCKED,
+ WorkInfo.State.CANCELLED
+ )
+ if (!it.isNullOrEmpty() && errorStates.contains(it.last().state)) {
+ binding.button.isEnabled = true
+ }
+ }
+ }
+
private fun showLoadingUI() {
binding.button.isEnabled = false
binding.noUpdates.visibility = View.GONE
diff --git a/app/src/main/java/foundation/e/apps/updates/UpdatesNotifier.kt b/app/src/main/java/foundation/e/apps/updates/UpdatesNotifier.kt
index 2eaa3325743355c6ac648d85d1400715dbd60f40..61a5116ebf1a32dcd8e3e33200e6a7f120cdf63c 100644
--- a/app/src/main/java/foundation/e/apps/updates/UpdatesNotifier.kt
+++ b/app/src/main/java/foundation/e/apps/updates/UpdatesNotifier.kt
@@ -29,15 +29,13 @@ import androidx.core.app.NotificationManagerCompat
import foundation.e.apps.MainActivity
import foundation.e.apps.R
-class UpdatesNotifier {
- companion object {
- const val UPDATES_NOTIFICATION_CLICK_EXTRA = "updates_notification_click_extra"
- private const val UPDATES_NOTIFICATION_ID = 76
- private const val UPDATES_NOTIFICATION_CHANNEL_ID = "updates_notification"
- private const val UPDATES_NOTIFICATION_CHANNEL_TITLE = "App updates"
- }
+object UpdatesNotifier {
+ const val UPDATES_NOTIFICATION_CLICK_EXTRA = "updates_notification_click_extra"
+ private const val UPDATES_NOTIFICATION_ID = 76
+ private const val UPDATES_NOTIFICATION_CHANNEL_ID = "updates_notification"
+ private const val UPDATES_NOTIFICATION_CHANNEL_TITLE = "App updates"
- private fun getNotification(
+ fun getNotification(
context: Context,
numberOfApps: Int,
installAutomatically: Boolean,
@@ -48,22 +46,31 @@ class UpdatesNotifier {
NotificationCompat.Builder(context, UPDATES_NOTIFICATION_CHANNEL_ID)
notificationBuilder.setSmallIcon(R.drawable.ic_app_updated_on)
notificationBuilder.priority = NotificationCompat.PRIORITY_DEFAULT
- if (numberOfApps == 1) {
- notificationBuilder.setContentTitle(
- context.resources.getQuantityString(
- R.plurals.updates_notification_title,
- 1,
- numberOfApps
+
+ when (numberOfApps) {
+ 0 -> {
+ notificationBuilder.setContentTitle(
+ "Checking Updates..."
)
- )
- } else {
- notificationBuilder.setContentTitle(
- context.resources.getQuantityString(
- R.plurals.updates_notification_title,
- numberOfApps,
- numberOfApps
+ }
+ 1 -> {
+ notificationBuilder.setContentTitle(
+ context.resources.getQuantityString(
+ R.plurals.updates_notification_title,
+ 1,
+ numberOfApps
+ )
)
- )
+ }
+ else -> {
+ notificationBuilder.setContentTitle(
+ context.resources.getQuantityString(
+ R.plurals.updates_notification_title,
+ numberOfApps,
+ numberOfApps
+ )
+ )
+ }
}
if (installAutomatically) {
notificationBuilder.setContentText(context.getString(R.string.automatically_install_updates_notification_text))
@@ -124,4 +131,10 @@ class UpdatesNotifier {
)
}
}
+
+ fun cancelNotification(context: Context) {
+ val notificationManager: NotificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancel(UPDATES_NOTIFICATION_ID)
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt
index ffd48afa1631b5db47d3ad74ea6a3197f6907286..5fd534f7ad62f1482751c1dc15916693f8fd578e 100644
--- a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt
+++ b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt
@@ -47,8 +47,10 @@ class UpdatesManagerImpl @Inject constructor(
if (pkgList.isNotEmpty()) {
// Get updates from CleanAPK
+ val openSourcePackages = userApplications.filter { !pkgManagerModule.isGplay(it.packageName) }.map { it.packageName }
+ pkgList.removeAll(openSourcePackages)
val cleanAPKResult = fusedAPIRepository.getApplicationDetails(
- pkgList,
+ openSourcePackages,
authData,
Origin.CLEANAPK
)
diff --git a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerRepository.kt b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerRepository.kt
index dadc807ca4a276286a39aba811090c4924e9c965..0805a8a5bd6b54b23c9745153b65a8361c1d7250 100644
--- a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerRepository.kt
+++ b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerRepository.kt
@@ -19,6 +19,7 @@
package foundation.e.apps.updates.manager
import com.aurora.gplayapi.data.models.AuthData
+import foundation.e.apps.api.fused.UpdatesDao
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.utils.enums.ResultStatus
import javax.inject.Inject
@@ -28,8 +29,12 @@ class UpdatesManagerRepository @Inject constructor(
) {
suspend fun getUpdates(authData: AuthData): Pair, ResultStatus> {
+ if (UpdatesDao.hasAnyAppsForUpdate()) {
+ return Pair(UpdatesDao.appsAwaitingForUpdate, ResultStatus.OK)
+ }
return updatesManagerImpl.getUpdates(authData).run {
val filteredApps = first.filter { !(!it.isFree && authData.isAnonymous) }
+ UpdatesDao.appsAwaitingForUpdate = filteredApps
Pair(filteredApps, this.second)
}
}
diff --git a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorkManager.kt b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorkManager.kt
index c70345a09b504d7d280b24677cde634997ed556b..f237e51c684630b4b0e2a7056465837bffe1bb3a 100644
--- a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorkManager.kt
+++ b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorkManager.kt
@@ -20,24 +20,28 @@ package foundation.e.apps.updates.manager
import android.content.Context
import android.util.Log
import androidx.work.Constraints
+import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
+import java.util.UUID
import java.util.concurrent.TimeUnit
object UpdatesWorkManager {
const val UPDATES_WORK_NAME = "updates_work"
private const val TAG = "UpdatesManager"
- fun startUpdateAllWork(context: Context) {
+ fun startUpdateAllWork(context: Context): UUID {
+ val oneTimeWorkRequest = buildOneTimeWorkRequest()
WorkManager.getInstance(context).enqueueUniqueWork(
UPDATES_WORK_NAME,
ExistingWorkPolicy.REPLACE,
buildOneTimeWorkRequest()
)
+ return oneTimeWorkRequest.id
}
private fun buildWorkerConstraints() = Constraints.Builder().apply {
@@ -52,13 +56,16 @@ object UpdatesWorkManager {
TimeUnit.HOURS
).apply {
setConstraints(buildWorkerConstraints())
+ addTag(TAG)
}.build()
}
private fun buildOneTimeWorkRequest(): OneTimeWorkRequest {
return OneTimeWorkRequest.Builder(UpdatesWorker::class.java).apply {
setConstraints(buildWorkerConstraints())
- }.build()
+ addTag(UPDATES_WORK_NAME)
+ }.setInputData(Data.Builder().putBoolean(UpdatesWorker.IS_AUTO_UPDATE, false).build())
+ .build()
}
fun enqueueWork(
diff --git a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt
index 46597fee660d8c09b7064fef1824a208f0cc417f..e8965a51d8f4624c9a4af43eab0379d74d14b453 100644
--- a/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt
+++ b/app/src/main/java/foundation/e/apps/updates/manager/UpdatesWorker.kt
@@ -34,39 +34,47 @@ import java.net.URL
@HiltWorker
class UpdatesWorker @AssistedInject constructor(
@Assisted private val context: Context,
- @Assisted params: WorkerParameters,
+ @Assisted private val params: WorkerParameters,
private val updatesManagerRepository: UpdatesManagerRepository,
private val fusedAPIRepository: FusedAPIRepository,
private val fusedManagerRepository: FusedManagerRepository,
private val dataStoreModule: DataStoreModule,
private val gson: Gson,
) : CoroutineWorker(context, params) {
+
+ companion object {
+ const val IS_AUTO_UPDATE = "IS_AUTO_UPDATE"
+ }
+
val TAG = UpdatesWorker::class.simpleName
private var shouldShowNotification = true
private var automaticInstallEnabled = true
private var onlyOnUnmeteredNetwork = false
+ private var isAutoUpdate = true // indicates it is auto update or user initiated update
override suspend fun doWork(): Result {
return try {
+ isAutoUpdate = params.inputData.getBoolean(IS_AUTO_UPDATE, true)
checkForUpdates()
Result.success()
} catch (e: Throwable) {
Result.failure()
+ } finally {
+ if (shouldShowNotification && automaticInstallEnabled) {
+ UpdatesNotifier.cancelNotification(context)
+ }
}
}
private suspend fun checkForUpdates() {
loadSettings()
+ val isConnectedToUnmeteredNetwork = isConnectedToUnmeteredNetwork(applicationContext)
val authData = getAuthData()
val appsNeededToUpdate = updatesManagerRepository.getUpdates(authData).first
- val isConnectedToUnmeteredNetwork = isConnectedToUnmeteredNetwork(applicationContext)
- /*
- * Show notification only if enabled.
- * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5376
- */
- if (shouldShowNotification) {
- handleNotification(appsNeededToUpdate, isConnectedToUnmeteredNetwork)
+ if (isAutoUpdate && shouldShowNotification) {
+ handleNotification(appsNeededToUpdate.size, isConnectedToUnmeteredNetwork)
}
+
triggerUpdateProcessOnSettings(
isConnectedToUnmeteredNetwork,
appsNeededToUpdate,
@@ -79,7 +87,7 @@ class UpdatesWorker @AssistedInject constructor(
appsNeededToUpdate: List,
authData: AuthData
) {
- if (automaticInstallEnabled &&
+ if ((!isAutoUpdate || automaticInstallEnabled) &&
applicationContext.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
) {
if (onlyOnUnmeteredNetwork && isConnectedToUnmeteredNetwork) {
@@ -91,13 +99,13 @@ class UpdatesWorker @AssistedInject constructor(
}
private fun handleNotification(
- appsNeededToUpdate: List,
+ numberOfAppsNeedUpdate: Int,
isConnectedToUnmeteredNetwork: Boolean
) {
- if (appsNeededToUpdate.isNotEmpty()) {
- UpdatesNotifier().showNotification(
+ if (numberOfAppsNeedUpdate > 0) {
+ UpdatesNotifier.showNotification(
applicationContext,
- appsNeededToUpdate.size,
+ numberOfAppsNeedUpdate,
automaticInstallEnabled,
onlyOnUnmeteredNetwork,
isConnectedToUnmeteredNetwork
diff --git a/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsFunctions.kt b/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsFunctions.kt
index e31a5efcd22a21943d51972e1ec91f117aef4290..adf4d1552c87cb632a778055fd3361daa2c80ce3 100644
--- a/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsFunctions.kt
+++ b/app/src/main/java/foundation/e/apps/utils/modules/CommonUtilsFunctions.kt
@@ -20,6 +20,7 @@ package foundation.e.apps.utils.modules
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
+import java.lang.Exception
object CommonUtilsFunctions {
diff --git a/app/src/test/java/foundation/e/apps/util/LiveDataTestUtil.kt b/app/src/test/java/foundation/e/apps/util/LiveDataTestUtil.kt
index 679cc04e20a3f9ee6028459e0d239150f0cf790a..58e478c3403c125ee73da31fa21ff648d4d1fcc1 100644
--- a/app/src/test/java/foundation/e/apps/util/LiveDataTestUtil.kt
+++ b/app/src/test/java/foundation/e/apps/util/LiveDataTestUtil.kt
@@ -20,8 +20,6 @@ package foundation.e.apps.util
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import kotlinx.coroutines.delay
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
/**
* Gets the value of a [LiveData] or waits for it to have one, with a timeout.