diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c6155ab22b06ab8bdabe8a83d0e2a94e6491f6b9..6cf905dd2403d6cf2e6e4364e19dd651fa8ea1bb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,6 +12,9 @@
+
+
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 3d6f64dbfc2f703494c72708bc100098a7724122..9309974f05a708b33bb6a3bc85104377cdfb6ac0 100644
--- a/app/src/main/java/foundation/e/apps/api/DownloadManager.kt
+++ b/app/src/main/java/foundation/e/apps/api/DownloadManager.kt
@@ -159,4 +159,29 @@ class DownloadManager @Inject constructor(
}
return DownloadManager.STATUS_FAILED
}
+
+ suspend fun checkDownloadProcess(downloadingIds: LongArray, handleFailed: suspend () -> Unit) {
+ try {
+ downloadManager.query(downloadManagerQuery.setFilterById(*downloadingIds))
+ .use { cursor ->
+
+ if (!cursor.moveToFirst()) {
+ return@use
+ }
+
+ while (!cursor.isAfterLast) {
+ val status =
+ cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
+
+ if (status == DownloadManager.STATUS_FAILED) {
+ handleFailed()
+ }
+
+ cursor.moveToNext()
+ }
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt
index db1a5a3fb25ebe6e1fb5de176ecf08a10a0aa834..61b941c3cd76f4208bb8b46fed14f88ecfd78b1d 100644
--- a/app/src/main/java/foundation/e/apps/home/HomeFragment.kt
+++ b/app/src/main/java/foundation/e/apps/home/HomeFragment.kt
@@ -43,7 +43,6 @@ import foundation.e.apps.home.model.HomeChildRVAdapter
import foundation.e.apps.home.model.HomeParentRVAdapter
import foundation.e.apps.login.AuthObject
import foundation.e.apps.manager.download.data.DownloadProgress
-import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.exceptions.GPlayException
import foundation.e.apps.utils.exceptions.GPlayLoginException
@@ -69,9 +68,6 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface
private val appProgressViewModel: AppProgressViewModel by viewModels()
private val appInfoFetchViewModel: AppInfoFetchViewModel by viewModels()
- @Inject
- lateinit var pkgManagerModule: PkgManagerModule
-
@Inject
lateinit var pwaManagerModule: PWAManagerModule
@@ -108,9 +104,6 @@ class HomeFragment : TimeoutFragment(R.layout.fragment_home), FusedAPIInterface
private fun initHomeParentRVAdapter() = HomeParentRVAdapter(
this,
- pkgManagerModule,
- pwaManagerModule,
- mainActivityViewModel.getUser(),
mainActivityViewModel, appInfoFetchViewModel, viewLifecycleOwner
) { fusedApp ->
if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) {
diff --git a/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt b/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt
index 76b4638dd4f8c8e1513bf9f99112417b74512e5f..07641183c2c567690b82bd11f35f3f07de966c41 100644
--- a/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt
+++ b/app/src/main/java/foundation/e/apps/home/model/HomeParentRVAdapter.kt
@@ -30,15 +30,9 @@ import foundation.e.apps.api.fused.FusedAPIInterface
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedHome
import foundation.e.apps.databinding.HomeParentListItemBinding
-import foundation.e.apps.manager.pkg.PkgManagerModule
-import foundation.e.apps.utils.enums.User
-import foundation.e.apps.utils.modules.PWAManagerModule
class HomeParentRVAdapter(
private val fusedAPIInterface: FusedAPIInterface,
- private val pkgManagerModule: PkgManagerModule,
- private val pwaManagerModule: PWAManagerModule,
- private val user: User,
private val mainActivityViewModel: MainActivityViewModel,
private val appInfoFetchViewModel: AppInfoFetchViewModel,
private var lifecycleOwner: LifecycleOwner?,
diff --git a/app/src/main/java/foundation/e/apps/manager/database/fusedDownload/FusedDownload.kt b/app/src/main/java/foundation/e/apps/manager/database/fusedDownload/FusedDownload.kt
index 7cf086c690c8b67ee4d9bced757b623b0de4c836..377ad26f77c38b04ae1cdaa66f157b9dd7e8bd3f 100644
--- a/app/src/main/java/foundation/e/apps/manager/database/fusedDownload/FusedDownload.kt
+++ b/app/src/main/java/foundation/e/apps/manager/database/fusedDownload/FusedDownload.kt
@@ -25,8 +25,10 @@ data class FusedDownload(
val appSize: Long = 0,
var files: List = mutableListOf(),
var signature: String = String()
-)
+) {
+ fun isAppInstalling() = listOf(Status.AWAITING, Status.DOWNLOADING, Status.DOWNLOADED, Status.INSTALLING).contains(status)
-fun FusedDownload.isAppInstalling() = listOf(Status.AWAITING, Status.DOWNLOADING, Status.DOWNLOADED, Status.INSTALLING).contains(status)
+ fun isAwaiting() = status == Status.AWAITING
-fun FusedDownload.isAwaiting() = status == Status.AWAITING
+ fun areFilesDownloaded() = downloadIdMap.isNotEmpty() && !downloadIdMap.values.contains(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 3cb93478cf1fb1f83ee37a0cb2f2b48574ba6419..a9ce748dfcc260ff0cb726f71896ef47437a4249 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
@@ -280,4 +280,12 @@ class FusedManagerImpl @Inject constructor(
fusedDownload.status = Status.PURCHASE_NEEDED
databaseRepository.addDownload(fusedDownload)
}
+
+ fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean {
+ return pkgManagerModule.isInstalled(fusedDownload.packageName)
+ }
+
+ fun getFusedDownloadInstallationStatus(fusedApp: FusedDownload): Status {
+ return pkgManagerModule.getPackageStatus(fusedApp.packageName, fusedApp.versionCode)
+ }
}
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 5e18d80c0bb17454942b5acf44865d380630b92e..c9bd1d6dd84c8ea371a627bd17992c525a328157 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
@@ -184,4 +184,12 @@ class FusedManagerRepository @Inject constructor(
val apkFilePath = fusedManagerImpl.getBaseApkPath(fusedDownload)
return fdroidRepository.isFdroidApplicationSigned(context, fusedDownload.packageName, apkFilePath, fusedDownload.signature)
}
+
+ fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean {
+ return fusedManagerImpl.isFusedDownloadInstalled(fusedDownload)
+ }
+
+ fun getFusedDownloadPackageStatus(fusedDownload: FusedDownload): Status {
+ return fusedManagerImpl.getFusedDownloadInstallationStatus(fusedDownload)
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b0c5b749bbdc1dbc7e45ca59439d21e93e39c6d7
--- /dev/null
+++ b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright MURENA SAS 2023
+ * 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.workmanager
+
+import android.content.Context
+import android.util.Log
+import dagger.hilt.android.qualifiers.ApplicationContext
+import foundation.e.apps.R
+import foundation.e.apps.api.DownloadManager
+import foundation.e.apps.api.fused.UpdatesDao
+import foundation.e.apps.manager.database.DatabaseRepository
+import foundation.e.apps.manager.database.fusedDownload.FusedDownload
+import foundation.e.apps.manager.fused.FusedManagerRepository
+import foundation.e.apps.updates.UpdatesNotifier
+import foundation.e.apps.utils.enums.ResultStatus
+import foundation.e.apps.utils.enums.Status
+import foundation.e.apps.utils.enums.Type
+import foundation.e.apps.utils.modules.DataStoreManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.sync.Mutex
+import timber.log.Timber
+import java.text.NumberFormat
+import java.text.SimpleDateFormat
+import java.util.Date
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+class AppInstallProcessor @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val databaseRepository: DatabaseRepository,
+ private val fusedManagerRepository: FusedManagerRepository,
+ private val downloadManager: DownloadManager,
+ private val dataStoreManager: DataStoreManager
+) {
+
+ private var isDownloading: Boolean = false
+ private var isItUpdateWork = false
+
+ companion object {
+ private const val TAG = "AppInstallProcessor"
+ }
+
+ private val mutex = Mutex(true)
+
+ suspend fun processInstall(
+ fusedDownloadId: String,
+ isItUpdateWork: Boolean,
+ runInForeground: (suspend (String) -> Unit)? = null
+ ): Result {
+ var fusedDownload: FusedDownload? = null
+ try {
+ Timber.d("Fused download name $fusedDownloadId")
+
+ fusedDownload = databaseRepository.getDownloadById(fusedDownloadId)
+ Timber.i(">>> dowork started for Fused download name " + fusedDownload?.name + " " + fusedDownloadId)
+
+ fusedDownload?.let {
+
+ this.isItUpdateWork = isItUpdateWork &&
+ fusedManagerRepository.isFusedDownloadInstalled(fusedDownload)
+
+ if (!fusedDownload.isAppInstalling()) {
+ Timber.d("!!! returned")
+ return@let
+ }
+
+ if (fusedDownload.areFilesDownloaded() && !fusedManagerRepository.isFusedDownloadInstalled(
+ fusedDownload
+ )
+ ) {
+ Timber.i("===> Downloaded But not installed ${fusedDownload.name}")
+ fusedManagerRepository.updateDownloadStatus(fusedDownload, Status.INSTALLING)
+ }
+
+ runInForeground?.invoke(it.name)
+
+ if (!fusedManagerRepository.validateFusedDownload(fusedDownload)) {
+ fusedManagerRepository.installationIssue(it)
+ Timber.d("!!! installationIssue")
+ return@let
+ }
+
+ startAppInstallationProcess(it)
+ mutex.lock()
+ }
+ } catch (e: Exception) {
+ Timber.e("doWork: Failed: ${e.stackTraceToString()}")
+ fusedDownload?.let {
+ fusedManagerRepository.installationIssue(it)
+ }
+ }
+
+ Timber.i("doWork: RESULT SUCCESS: ${fusedDownload?.name}")
+ return Result.success(ResultStatus.OK)
+ }
+
+ private suspend fun checkUpdateWork(
+ fusedDownload: FusedDownload?
+ ) {
+ if (isItUpdateWork) {
+ fusedDownload?.let {
+ val packageStatus = fusedManagerRepository.getFusedDownloadPackageStatus(fusedDownload)
+
+ if (packageStatus == Status.INSTALLED) {
+ UpdatesDao.addSuccessfullyUpdatedApp(it)
+ }
+
+ if (isUpdateCompleted()) { // show notification for ended update
+ showNotificationOnUpdateEnded()
+ UpdatesDao.clearSuccessfullyUpdatedApps()
+ }
+ }
+ }
+ }
+
+ private suspend fun isUpdateCompleted(): Boolean {
+ val downloadListWithoutAnyIssue =
+ databaseRepository.getDownloadList()
+ .filter {
+ !listOf(
+ Status.INSTALLATION_ISSUE,
+ Status.PURCHASE_NEEDED
+ ).contains(it.status)
+ }
+
+ return UpdatesDao.successfulUpdatedApps.isNotEmpty() && downloadListWithoutAnyIssue.isEmpty()
+ }
+
+ private fun showNotificationOnUpdateEnded() {
+ val date = Date(System.currentTimeMillis())
+ val locale = dataStoreManager.getAuthData().locale
+ val dateFormat =
+ SimpleDateFormat("dd/MM/yyyy-HH:mm", locale)
+ val numberOfUpdatedApps = NumberFormat.getNumberInstance(locale)
+ .format(UpdatesDao.successfulUpdatedApps.size)
+ .toString()
+
+ UpdatesNotifier.showNotification(
+ context, context.getString(R.string.update),
+ context.getString(
+ R.string.message_last_update_triggered, numberOfUpdatedApps, dateFormat.format(date)
+ )
+ )
+ }
+
+ private suspend fun startAppInstallationProcess(
+ fusedDownload: FusedDownload
+ ): Boolean {
+ if (fusedDownload.isAwaiting()) {
+ fusedManagerRepository.downloadApp(fusedDownload)
+ Timber.i("===> doWork: Download started ${fusedDownload.name} ${fusedDownload.status}")
+ }
+
+ isDownloading = true
+ /**
+ * observe app download/install process in a separate thread as DownloadManager download artifacts in a separate process
+ * It checks install status every three seconds
+ */
+ tickerFlow(3.seconds)
+ .onEach {
+ val download = databaseRepository.getDownloadById(fusedDownload.id)
+ if (download == null) {
+ finishInstallation(fusedDownload)
+ } else {
+ handleFusedDownloadStatusCheckingException(download)
+ if (isAppDownloading(download)) {
+ checkDownloadProcess(download)
+ }
+ }
+ }.launchIn(CoroutineScope(Dispatchers.IO))
+ Timber.d(">>> ===> doWork: Download started " + fusedDownload.name + " " + fusedDownload.status)
+ return true
+ }
+
+ private fun isAppDownloading(download: FusedDownload): Boolean {
+ return download.type == Type.NATIVE && download.status != Status.INSTALLED && download.status != Status.INSTALLATION_ISSUE
+ }
+
+ private suspend fun handleFusedDownloadStatusCheckingException(
+ download: FusedDownload
+ ) {
+ try {
+ handleFusedDownloadStatus(download)
+ } catch (e: Exception) {
+ Log.e(TAG, "observeDownload: ", e)
+ finishInstallation(download)
+ }
+ }
+
+ /**
+ * Triggers a repetitive event according to the delay passed in the parameter
+ * @param period delay of each event
+ * @param initialDelay initial delay to trigger the first event
+ */
+ private fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
+ delay(initialDelay)
+ while (isDownloading) {
+ emit(Unit)
+ delay(period)
+ }
+ }
+
+ private suspend fun checkDownloadProcess(fusedDownload: FusedDownload) {
+ downloadManager.checkDownloadProcess(fusedDownload.downloadIdMap.keys.toLongArray()) {
+ fusedManagerRepository.installationIssue(fusedDownload)
+ }
+ }
+
+ private suspend fun handleFusedDownloadStatus(fusedDownload: FusedDownload) {
+ when (fusedDownload.status) {
+ Status.AWAITING, Status.DOWNLOADING -> {
+ }
+ Status.DOWNLOADED -> {
+ fusedManagerRepository.updateDownloadStatus(fusedDownload, Status.INSTALLING)
+ }
+ Status.INSTALLING -> {
+ Timber.i("===> doWork: Installing ${fusedDownload.name} ${fusedDownload.status}")
+ }
+ Status.INSTALLED, Status.INSTALLATION_ISSUE -> {
+ finishInstallation(fusedDownload)
+ Timber.i("===> doWork: Installed/Failed: ${fusedDownload.name} ${fusedDownload.status}")
+ }
+ else -> {
+ finishInstallation(fusedDownload)
+ Timber.wtf(
+ TAG,
+ "===> ${fusedDownload.name} is in wrong state ${fusedDownload.status}"
+ )
+ }
+ }
+ }
+
+ private suspend fun finishInstallation(fusedDownload: FusedDownload) {
+ checkUpdateWork(fusedDownload)
+ isDownloading = false
+ unlockMutex()
+ }
+
+ private fun unlockMutex() {
+ if (mutex.isLocked) {
+ mutex.unlock()
+ }
+ }
+}
diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
index fd7f68da56426aa375c95de4a5061e1f3abf75e6..01fc3e291d2d72ef65c26bf434d88c949a8b62f4 100644
--- a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
+++ b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
@@ -18,12 +18,10 @@
package foundation.e.apps.manager.workmanager
-import android.app.DownloadManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
-import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
@@ -33,47 +31,15 @@ import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import foundation.e.apps.R
-import foundation.e.apps.api.fused.UpdatesDao
-import foundation.e.apps.manager.database.DatabaseRepository
-import foundation.e.apps.manager.database.fusedDownload.FusedDownload
-import foundation.e.apps.manager.database.fusedDownload.isAppInstalling
-import foundation.e.apps.manager.database.fusedDownload.isAwaiting
-import foundation.e.apps.manager.fused.FusedManagerRepository
-import foundation.e.apps.manager.pkg.PkgManagerModule
-import foundation.e.apps.updates.UpdatesNotifier
-import foundation.e.apps.utils.enums.Status
-import foundation.e.apps.utils.enums.Type
-import foundation.e.apps.utils.modules.DataStoreManager
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.sync.Mutex
-import timber.log.Timber
-import java.text.NumberFormat
-import java.text.SimpleDateFormat
-import java.util.Date
import java.util.concurrent.atomic.AtomicInteger
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
@HiltWorker
class InstallAppWorker @AssistedInject constructor(
@Assisted private val context: Context,
@Assisted private val params: WorkerParameters,
- private val databaseRepository: DatabaseRepository,
- private val fusedManagerRepository: FusedManagerRepository,
- private val downloadManager: DownloadManager,
- private val downloadManagerQuery: DownloadManager.Query,
- private val packageManagerModule: PkgManagerModule,
- private val dataStoreManager: DataStoreManager
+ private val appInstallProcessor: AppInstallProcessor
) : CoroutineWorker(context, params) {
- private var isDownloading: Boolean = false
- private var isItUpdateWork = false
-
companion object {
private const val TAG = "InstallWorker"
const val INPUT_DATA_FUSED_DOWNLOAD = "input_data_fused_download"
@@ -91,209 +57,17 @@ class InstallAppWorker @AssistedInject constructor(
private val atomicInteger = AtomicInteger(100)
}
- private val mutex = Mutex(true)
-
override suspend fun doWork(): Result {
- var fusedDownload: FusedDownload? = null
- try {
- val fusedDownloadString = params.inputData.getString(INPUT_DATA_FUSED_DOWNLOAD) ?: ""
- Timber.d("Fused download name $fusedDownloadString")
-
- fusedDownload = databaseRepository.getDownloadById(fusedDownloadString)
- Timber.i(">>> dowork started for Fused download name " + fusedDownload?.name + " " + fusedDownloadString)
- fusedDownload?.let {
- isItUpdateWork = params.inputData.getBoolean(IS_UPDATE_WORK, false) &&
- packageManagerModule.isInstalled(it.packageName)
-
- if (!fusedDownload.isAppInstalling()) {
- return Result.success()
- }
-
- setForeground(
- createForegroundInfo(
- "Installing ${it.name}"
- )
- )
-
- if (!fusedManagerRepository.validateFusedDownload(fusedDownload)) {
- fusedManagerRepository.installationIssue(it)
- return@let
- }
-
- startAppInstallationProcess(it)
- mutex.lock()
- }
- } catch (e: Exception) {
- Timber.e("doWork: Failed: ${e.stackTraceToString()}")
- fusedDownload?.let {
- fusedManagerRepository.installationIssue(it)
- }
- } finally {
- Timber.i("doWork: RESULT SUCCESS: ${fusedDownload?.name}")
- return Result.success()
- }
- }
-
- private suspend fun InstallAppWorker.checkUpdateWork(
- fusedDownload: FusedDownload?
- ) {
- if (isItUpdateWork) {
- fusedDownload?.let {
- val packageStatus = packageManagerModule.getPackageStatus(
- fusedDownload.packageName,
- fusedDownload.versionCode
+ val fusedDownloadId = params.inputData.getString(INPUT_DATA_FUSED_DOWNLOAD) ?: ""
+ val isPackageUpdate = params.inputData.getBoolean(IS_UPDATE_WORK, false)
+ appInstallProcessor.processInstall(fusedDownloadId, isPackageUpdate) { title ->
+ setForeground(
+ createForegroundInfo(
+ "Installing $title"
)
-
- if (packageStatus == Status.INSTALLED) {
- UpdatesDao.addSuccessfullyUpdatedApp(it)
- }
-
- if (isUpdateCompleted()) { // show notification for ended update
- showNotificationOnUpdateEnded()
- UpdatesDao.clearSuccessfullyUpdatedApps()
- }
- }
- }
- }
-
- private suspend fun isUpdateCompleted(): Boolean {
- val downloadListWithoutAnyIssue =
- databaseRepository.getDownloadList()
- .filter {
- !listOf(
- Status.INSTALLATION_ISSUE,
- Status.PURCHASE_NEEDED
- ).contains(it.status)
- }
-
- return UpdatesDao.successfulUpdatedApps.isNotEmpty() && downloadListWithoutAnyIssue.isEmpty()
- }
-
- private fun showNotificationOnUpdateEnded() {
- val date = Date(System.currentTimeMillis())
- val locale = dataStoreManager.getAuthData().locale
- val dateFormat =
- SimpleDateFormat("dd/MM/yyyy-HH:mm", locale)
- val numberOfUpdatedApps = NumberFormat.getNumberInstance(locale)
- .format(UpdatesDao.successfulUpdatedApps.size)
- .toString()
-
- UpdatesNotifier.showNotification(
- context, context.getString(R.string.update),
- context.getString(
- R.string.message_last_update_triggered, numberOfUpdatedApps, dateFormat.format(date)
)
- )
- }
-
- private suspend fun startAppInstallationProcess(
- fusedDownload: FusedDownload
- ) {
- if (fusedDownload.isAwaiting()) {
- fusedManagerRepository.downloadApp(fusedDownload)
- Timber.i("===> doWork: Download started ${fusedDownload.name} ${fusedDownload.status}")
- }
- isDownloading = true
- /**
- * observe app download/install process in a separate thread as DownloadManager download artifacts in a separate process
- * It checks install status every three seconds
- */
- tickerFlow(3.seconds)
- .onEach {
- val download = databaseRepository.getDownloadById(fusedDownload.id)
-
- if (download == null) {
- finishInstallation(fusedDownload)
- } else {
- handleFusedDownloadStatusCheckingException(download)
- if (isAppDownloading(download)) {
- checkDownloadProcess(download)
- }
- }
- }.launchIn(CoroutineScope(Dispatchers.IO))
- Timber.d(">>> ===> doWork: Download started " + fusedDownload.name + " " + fusedDownload.status)
- }
-
- private fun isAppDownloading(download: FusedDownload): Boolean {
- return download.type == Type.NATIVE && download.status != Status.INSTALLED && download.status != Status.INSTALLATION_ISSUE
- }
-
- private suspend fun handleFusedDownloadStatusCheckingException(
- download: FusedDownload
- ) {
- try {
- handleFusedDownloadStatus(download)
- } catch (e: Exception) {
- Log.e(TAG, "observeDownload: ", e)
- finishInstallation(download)
- }
- }
-
- /**
- * Triggers a repetitive event according to the delay passed in the parameter
- * @param period delay of each event
- * @param initialDelay initial delay to trigger the first event
- */
- private fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
- delay(initialDelay)
- while (isDownloading) {
- emit(Unit)
- delay(period)
- }
- }
-
- private suspend fun checkDownloadProcess(fusedDownload: FusedDownload) {
- try {
- downloadManager.query(downloadManagerQuery.setFilterById(*fusedDownload.downloadIdMap.keys.toLongArray()))
- .use { cursor ->
- if (cursor.moveToFirst()) {
- val status =
- cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
-
- if (status == DownloadManager.STATUS_FAILED) {
- fusedManagerRepository.installationIssue(fusedDownload)
- }
- }
- }
- } catch (e: Exception) {
- Timber.e(e)
- }
- }
-
- private suspend fun handleFusedDownloadStatus(fusedDownload: FusedDownload) {
- when (fusedDownload.status) {
- Status.AWAITING, Status.DOWNLOADING -> {
- }
- Status.DOWNLOADED -> {
- fusedManagerRepository.updateDownloadStatus(fusedDownload, Status.INSTALLING)
- }
- Status.INSTALLING -> {
- Timber.i("===> doWork: Installing ${fusedDownload.name} ${fusedDownload.status}")
- }
- Status.INSTALLED, Status.INSTALLATION_ISSUE -> {
- finishInstallation(fusedDownload)
- Timber.i("===> doWork: Installed/Failed: ${fusedDownload.name} ${fusedDownload.status}")
- }
- else -> {
- finishInstallation(fusedDownload)
- Log.wtf(
- TAG,
- "===> ${fusedDownload.name} is in wrong state ${fusedDownload.status}"
- )
- }
- }
- }
-
- private suspend fun finishInstallation(fusedDownload: FusedDownload) {
- checkUpdateWork(fusedDownload)
- isDownloading = false
- unlockMutex()
- }
-
- private fun unlockMutex() {
- if (mutex.isLocked) {
- mutex.unlock()
}
+ return Result.success()
}
private fun createForegroundInfo(progress: String): ForegroundInfo {
diff --git a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt
index e995e3e5a31705ddd8fac0ddf73dbed83955a04f..4e121ea53bea705dd478d98ef35449798e4d43cd 100644
--- a/app/src/main/java/foundation/e/apps/search/SearchFragment.kt
+++ b/app/src/main/java/foundation/e/apps/search/SearchFragment.kt
@@ -54,7 +54,6 @@ import foundation.e.apps.databinding.FragmentSearchBinding
import foundation.e.apps.login.AuthObject
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.download.data.DownloadProgress
-import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.exceptions.GPlayLoginException
import foundation.e.apps.utils.modules.PWAManagerModule
@@ -69,9 +68,6 @@ class SearchFragment :
SearchView.OnSuggestionListener,
FusedAPIInterface {
- @Inject
- lateinit var pkgManagerModule: PkgManagerModule
-
@Inject
lateinit var pwaManagerModule: PWAManagerModule
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 80273b8c8f8835ae51540c371e84075ac7b0440f..28f78d80aa69eedc07ad452fef0137bd7cef2066 100644
--- a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt
+++ b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt
@@ -47,7 +47,6 @@ import foundation.e.apps.databinding.FragmentUpdatesBinding
import foundation.e.apps.login.AuthObject
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.download.data.DownloadProgress
-import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.manager.workmanager.InstallWorkManager.INSTALL_WORK_NAME
import foundation.e.apps.updates.manager.UpdatesWorkManager
import foundation.e.apps.utils.enums.ResultStatus
@@ -72,9 +71,6 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte
private var _binding: FragmentUpdatesBinding? = null
private val binding get() = _binding!!
- @Inject
- lateinit var pkgManagerModule: PkgManagerModule
-
@Inject
lateinit var pwaManagerModule: PWAManagerModule
diff --git a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt
index 61585e56c9059338f592a40d5c1872f8df0cc56c..8f6c519c8803f40d33824dad6f9f6356e14569b1 100644
--- a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt
+++ b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt
@@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.aurora.gplayapi.data.models.AuthData
import foundation.e.apps.api.faultyApps.FaultyAppRepository
+import foundation.e.apps.api.fused.FusedAPIImpl
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.manager.pkg.PkgManagerModule
@@ -84,44 +85,14 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenUpdateIsAvailable() = runTest {
- val gplayApps = mutableListOf(
- FusedApp(
- _id = "111",
- status = Status.UPDATABLE,
- name = "Demo One",
- package_name = "foundation.e.demoone",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- FusedApp(
- _id = "112",
- status = Status.INSTALLED,
- name = "Demo Two",
- package_name = "foundation.e.demotwo",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- )
-
- val openSourceApps = mutableListOf(
- FusedApp(
- _id = "113",
- status = Status.UPDATABLE,
- name = "Demo Three",
- package_name = "foundation.e.demothree",
- origin = Origin.CLEANAPK,
- filterLevel = FilterLevel.NONE
- )
- )
+ val gplayApps = getGplayApps()
+ val openSourceApps = getOpenSourceApps(Status.UPDATABLE)
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
val gplayUpdates = Pair(gplayApps, ResultStatus.OK)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -132,6 +103,25 @@ class UpdateManagerImptTest {
assertEquals("fetchUpdate", 2, updateResult.first.size)
}
+ private fun getGplayApps(status: Status = Status.UPDATABLE) = mutableListOf(
+ FusedApp(
+ _id = "111",
+ status = status,
+ name = "Demo One",
+ package_name = "foundation.e.demoone",
+ origin = Origin.GPLAY,
+ filterLevel = FilterLevel.NONE
+ ),
+ FusedApp(
+ _id = "112",
+ status = Status.INSTALLED,
+ name = "Demo Two",
+ package_name = "foundation.e.demotwo",
+ origin = Origin.GPLAY,
+ filterLevel = FilterLevel.NONE
+ ),
+ )
+
@Test
fun getUpdateWhenInstalledPackageListIsEmpty() = runTest {
val authData = AuthData("e@e.email", "AtadyMsIAtadyM")
@@ -146,44 +136,14 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenUpdateIsUnavailable() = runTest {
- val gplayApps = mutableListOf(
- FusedApp(
- _id = "111",
- status = Status.INSTALLED,
- name = "Demo One",
- package_name = "foundation.e.demoone",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- FusedApp(
- _id = "112",
- status = Status.INSTALLED,
- name = "Demo Two",
- package_name = "foundation.e.demotwo",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- )
+ val gplayApps = getGplayApps(Status.INSTALLED)
+ val openSourceApps = getOpenSourceApps(Status.INSTALLED)
- val openSourceApps = mutableListOf(
- FusedApp(
- _id = "113",
- status = Status.INSTALLED,
- name = "Demo Three",
- package_name = "foundation.e.demothree",
- origin = Origin.CLEANAPK,
- filterLevel = FilterLevel.NONE
- )
- )
-
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
val gplayUpdates = Pair(gplayApps, ResultStatus.OK)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -196,44 +156,14 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenUpdateHasOnlyForOpenSourceApps() = runTest {
- val gplayApps = mutableListOf(
- FusedApp(
- _id = "111",
- status = Status.INSTALLED,
- name = "Demo One",
- package_name = "foundation.e.demoone",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- FusedApp(
- _id = "112",
- status = Status.INSTALLED,
- name = "Demo Two",
- package_name = "foundation.e.demotwo",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- )
-
- val openSourceApps = mutableListOf(
- FusedApp(
- _id = "113",
- status = Status.UPDATABLE,
- name = "Demo Three",
- package_name = "foundation.e.demothree",
- origin = Origin.CLEANAPK,
- filterLevel = FilterLevel.NONE
- )
- )
+ val gplayApps = getGplayApps(Status.INSTALLED)
+ val openSourceApps = getOpenSourceApps(Status.UPDATABLE)
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
val gplayUpdates = Pair(gplayApps, ResultStatus.OK)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -246,44 +176,14 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenUpdateHasOnlyForGplayApps() = runTest {
- val gplayApps = mutableListOf(
- FusedApp(
- _id = "111",
- status = Status.INSTALLED,
- name = "Demo One",
- package_name = "foundation.e.demoone",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- FusedApp(
- _id = "112",
- status = Status.UPDATABLE,
- name = "Demo Two",
- package_name = "foundation.e.demotwo",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- )
+ val gplayApps = getGplayApps(Status.UPDATABLE)
+ val openSourceApps = getOpenSourceApps(Status.INSTALLED)
- val openSourceApps = mutableListOf(
- FusedApp(
- _id = "113",
- status = Status.INSTALLED,
- name = "Demo Three",
- package_name = "foundation.e.demothree",
- origin = Origin.CLEANAPK,
- filterLevel = FilterLevel.NONE
- )
- )
-
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
val gplayUpdates = Pair(gplayApps, ResultStatus.OK)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -296,35 +196,14 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenFetchingOpenSourceIsFailed() = runTest {
- val gplayApps = mutableListOf(
- FusedApp(
- _id = "111",
- status = Status.INSTALLED,
- name = "Demo One",
- package_name = "foundation.e.demoone",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- FusedApp(
- _id = "112",
- status = Status.UPDATABLE,
- name = "Demo Two",
- package_name = "foundation.e.demotwo",
- origin = Origin.GPLAY,
- filterLevel = FilterLevel.NONE
- ),
- )
-
+ val gplayApps = getGplayApps(Status.UPDATABLE)
val openSourceApps = mutableListOf()
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.TIMEOUT)
val gplayUpdates = Pair(gplayApps, ResultStatus.OK)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -339,26 +218,13 @@ class UpdateManagerImptTest {
@Test
fun getUpdateWhenFetchingGplayIsFailed() = runTest {
val gplayApps = mutableListOf()
+ val openSourceApps = getOpenSourceApps(Status.UPDATABLE)
- val openSourceApps = mutableListOf(
- FusedApp(
- _id = "113",
- status = Status.UPDATABLE,
- name = "Demo Three",
- package_name = "foundation.e.demothree",
- origin = Origin.CLEANAPK,
- filterLevel = FilterLevel.NONE
- )
- )
-
- val appList = gplayApps + openSourceApps
val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
val gplayUpdates = Pair(gplayApps, ResultStatus.TIMEOUT)
setupMockingForFetchingUpdates(
applicationInfo,
- appList,
- authData,
openSourceUpdates,
gplayUpdates
)
@@ -370,26 +236,85 @@ class UpdateManagerImptTest {
assertEquals("fetchupdate", ResultStatus.TIMEOUT, updateResult.second)
}
+ private fun getOpenSourceApps(status: Status = Status.UPDATABLE) = mutableListOf(
+ FusedApp(
+ _id = "113",
+ status = status,
+ name = "Demo Three",
+ package_name = "foundation.e.demothree",
+ origin = Origin.CLEANAPK,
+ filterLevel = FilterLevel.NONE
+ )
+ )
+
+ @Test
+ fun getUpdatesOSSWhenUpdateIsAvailable() = runTest {
+ val openSourceApps = getOpenSourceApps(Status.UPDATABLE)
+ val gPlayApps = getGplayApps(Status.UPDATABLE)
+
+ val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
+ val gplayUpdates = Pair(gPlayApps, ResultStatus.OK)
+
+ setupMockingForFetchingUpdates(applicationInfo, openSourceUpdates, gplayUpdates)
+
+ val updateResult = updatesManagerImpl.getUpdatesOSS()
+ assertEquals("UpdateOSS", 1, updateResult.first.size)
+ assertEquals("UpdateOSS", Origin.CLEANAPK, updateResult.first[0].origin)
+ }
+
+ @Test
+ fun getUpdatesOSSWhenUpdateIsUnavailable() = runTest {
+ val openSourceApps = getOpenSourceApps(Status.INSTALLED)
+ val gPlayApps = getGplayApps(Status.UPDATABLE)
+
+ val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK)
+ val gplayUpdates = Pair(gPlayApps, ResultStatus.OK)
+
+ setupMockingForFetchingUpdates(applicationInfo, openSourceUpdates, gplayUpdates)
+
+ val updateResult = updatesManagerImpl.getUpdatesOSS()
+ assertEquals("UpdateOSS", 0, updateResult.first.size)
+ }
+
+ @Test
+ fun getUpdatesOSSWhenOpenSourceIsFailed() = runTest {
+ val openSourceApps = mutableListOf()
+ val gPlayApps = getGplayApps(Status.UPDATABLE)
+
+ val openSourceUpdates = Pair(openSourceApps, ResultStatus.TIMEOUT)
+ val gplayUpdates = Pair(gPlayApps, ResultStatus.OK)
+
+ setupMockingForFetchingUpdates(applicationInfo, openSourceUpdates, gplayUpdates)
+
+ val updateResult = updatesManagerImpl.getUpdatesOSS()
+ assertEquals("UpdateOSS", 0, updateResult.first.size)
+ assertEquals("UpdateOSS", ResultStatus.TIMEOUT, updateResult.second)
+ }
+
private suspend fun setupMockingForFetchingUpdates(
applicationInfo: MutableList,
- appList: List,
- authData: AuthData,
openSourceUpdates: Pair, ResultStatus>,
- gplayUpdates: Pair, ResultStatus>
+ gplayUpdates: Pair, ResultStatus>,
+ selectedApplicationSources: List = mutableListOf(
+ FusedAPIImpl.APP_TYPE_ANY,
+ FusedAPIImpl.APP_TYPE_OPEN,
+ FusedAPIImpl.APP_TYPE_PWA
+ )
) {
Mockito.`when`(pkgManagerModule.getAllUserApps()).thenReturn(applicationInfo)
Mockito.`when`(
fusedAPIRepository.getApplicationDetails(
any(),
- eq(authData),
+ any(),
eq(Origin.CLEANAPK)
)
).thenReturn(openSourceUpdates)
-
+ Mockito.`when`(fusedAPIRepository.getApplicationCategoryPreference())
+ .thenReturn(selectedApplicationSources)
Mockito.`when`(
fusedAPIRepository.getApplicationDetails(
any(),
- eq(authData),
+ any(),
eq(Origin.GPLAY)
)
).thenReturn(gplayUpdates)