From a87c7d907c4942e08a97792a3c24b5a70328c4dd Mon Sep 17 00:00:00 2001 From: hasibprince Date: Thu, 2 Feb 2023 14:35:12 +0600 Subject: [PATCH 1/3] fixed: app is downloaded but not installed case --- app/src/main/AndroidManifest.xml | 3 + .../foundation/e/apps/api/DownloadManager.kt | 23 ++ .../foundation/e/apps/home/HomeFragment.kt | 7 - .../e/apps/home/model/HomeParentRVAdapter.kt | 6 - .../database/fusedDownload/FusedDownload.kt | 8 +- .../e/apps/manager/fused/FusedManagerImpl.kt | 8 + .../manager/fused/FusedManagerRepository.kt | 8 + .../workmanager/AppInstallProcessor.kt | 266 ++++++++++++++++++ .../manager/workmanager/InstallAppWorker.kt | 242 +--------------- .../e/apps/search/SearchFragment.kt | 4 - .../e/apps/updates/UpdatesFragment.kt | 4 - 11 files changed, 321 insertions(+), 258 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c6155ab22..6cf905dd2 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 3d6f64dbf..a9d3b68b4 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,27 @@ 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 db1a5a3fb..61b941c3c 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 76b4638dd..07641183c 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 7cf086c69..377ad26f7 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 3cb93478c..a9ce748df 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 5e18d80c0..c9bd1d6dd 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 000000000..f2025bc67 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt @@ -0,0 +1,266 @@ +/* + * 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.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 fd7f68da5..01fc3e291 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 e995e3e5a..4e121ea53 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 80273b8c8..28f78d80a 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 -- GitLab From eb16595b8e6630b430c1a92e67aa1df5e6b6d37b Mon Sep 17 00:00:00 2001 From: hasibprince Date: Tue, 7 Feb 2023 09:25:48 +0600 Subject: [PATCH 2/3] unit tests updated for UpdatesRepository --- .../e/apps/UpdateManagerImptTest.kt | 265 +++++++----------- 1 file changed, 95 insertions(+), 170 deletions(-) diff --git a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt index 61585e56c..8f6c519c8 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) -- GitLab From e10d8cdfe72102358edde4858e21a368eab28808 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 7 Feb 2023 09:45:22 +0000 Subject: [PATCH 3/3] updated copyright --- app/src/main/java/foundation/e/apps/api/DownloadManager.kt | 2 ++ .../e/apps/manager/workmanager/AppInstallProcessor.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 a9d3b68b4..9309974f0 100644 --- a/app/src/main/java/foundation/e/apps/api/DownloadManager.kt +++ b/app/src/main/java/foundation/e/apps/api/DownloadManager.kt @@ -164,9 +164,11 @@ class DownloadManager @Inject constructor( 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)) 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 index f2025bc67..b0c5b749b 100644 --- a/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/manager/workmanager/AppInstallProcessor.kt @@ -1,5 +1,5 @@ /* - * Copyright ECORP SAS 2022 + * 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 -- GitLab