From 4d66ab333c7e7c0d09265a08ab82cfc8d977f207 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Thu, 9 Feb 2023 12:42:31 +0600 Subject: [PATCH 1/2] unit test added for app install process --- app/build.gradle | 9 +- .../foundation/e/apps/api/DownloadManager.kt | 2 + .../e/apps/api/fdroid/FdroidRepository.kt | 10 +- .../e/apps/api/fdroid/IFdroidRepository.kt | 44 ++++ .../e/apps/application/ApplicationFragment.kt | 2 +- .../ApplicationListRVAdapter.kt | 1 + .../foundation/e/apps/di/RepositoryModule.kt | 12 ++ .../e/apps/home/model/HomeChildRVAdapter.kt | 1 + .../e/apps/manager/fused/FusedManagerImpl.kt | 50 ++--- .../manager/fused/FusedManagerRepository.kt | 4 +- .../e/apps/manager/fused/IFusedManager.kt | 82 ++++++++ .../workmanager/AppInstallProcessor.kt | 16 +- .../e/apps/updates/manager/UpdatesWorker.kt | 12 +- .../e/apps/FusedApiRepositoryTest.kt | 8 +- .../e/apps/fusedManager/FakeFusedManager.kt | 130 ++++++++++++ .../FusedManagerRepositoryTest.kt | 126 +++++++++++ .../AppInstallProcessorTest.kt | 195 ++++++++++++++++++ .../installProcessor/FakeDownloadManager.kt | 37 ++++ .../installProcessor/FakeFusedDownloadDAO.kt | 55 +++++ .../FakeFusedManagerRepository.kt | 103 +++++++++ build.gradle | 6 +- 21 files changed, 851 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/api/fdroid/IFdroidRepository.kt create mode 100644 app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt create mode 100644 app/src/test/java/foundation/e/apps/fusedManager/FakeFusedManager.kt create mode 100644 app/src/test/java/foundation/e/apps/fusedManager/FusedManagerRepositoryTest.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/FakeDownloadManager.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/FakeFusedDownloadDAO.kt create mode 100644 app/src/test/java/foundation/e/apps/installProcessor/FakeFusedManagerRepository.kt diff --git a/app/build.gradle b/app/build.gradle index dcef0e91c..f82c595bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,4 @@ + plugins { id 'com.android.application' id 'kotlin-android' @@ -153,6 +154,8 @@ dependencies { testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation "androidx.arch.core:core-testing:2.1.0" + testImplementation "io.mockk:mockk:1.13.4" + // Coil and PhotoView implementation "io.coil-kt:coil:1.4.0" implementation 'com.github.Baseflow:PhotoView:2.3.0' @@ -187,19 +190,19 @@ dependencies { implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2" // Navigation Components - def navigation_version = "2.3.5" + def navigation_version = "2.5.3" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" // Hilt - def hilt_version = '2.40.5' + def hilt_version = '2.44.2' kapt "com.google.dagger:hilt-compiler:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version" implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0' // Lifecycle Components - def lifecycle_version = "2.4.0" + def lifecycle_version = "2.5.1" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "android.arch.lifecycle:extensions:1.1.1" 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 9309974f0..e95d59f9a 100644 --- a/app/src/main/java/foundation/e/apps/api/DownloadManager.kt +++ b/app/src/main/java/foundation/e/apps/api/DownloadManager.kt @@ -19,6 +19,7 @@ package foundation.e.apps.api import android.app.DownloadManager import android.net.Uri +import foundation.e.apps.OpenForTesting import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -34,6 +35,7 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @Singleton +@OpenForTesting class DownloadManager @Inject constructor( private val downloadManager: DownloadManager, @Named("cacheDir") private val cacheDir: String, diff --git a/app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt b/app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt index 9990db75b..beba5d5b2 100644 --- a/app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/fdroid/FdroidRepository.kt @@ -12,7 +12,7 @@ import javax.inject.Singleton class FdroidRepository @Inject constructor( private val fdroidApi: FdroidApiInterface, private val fdroidDao: FdroidDao, -) { +) : IFdroidRepository { companion object { const val UNKNOWN = "unknown" @@ -26,7 +26,7 @@ class FdroidRepository @Inject constructor( * * Result may be null. */ - private suspend fun getFdroidInfo(packageName: String): FdroidEntity? { + override suspend fun getFdroidInfo(packageName: String): FdroidEntity? { return fdroidDao.getFdroidEntityFromPackageName(packageName) ?: fdroidApi.getFdroidInfoForPackage(packageName).body()?.let { FdroidEntity(packageName, it.authorName).also { @@ -35,7 +35,7 @@ class FdroidRepository @Inject constructor( } } - suspend fun getAuthorName(fusedApp: FusedApp): String { + override suspend fun getAuthorName(fusedApp: FusedApp): String { if (fusedApp.author != UNKNOWN || fusedApp.origin != Origin.CLEANAPK) { return fusedApp.author.ifEmpty { UNKNOWN } } @@ -49,14 +49,14 @@ class FdroidRepository @Inject constructor( return result?.authorName ?: FdroidEntity.DEFAULT_FDROID_AUTHOR_NAME } - suspend fun isFdroidApplicationSigned(context: Context, packageName: String, apkFilePath: String, signature: String): Boolean { + override suspend fun isFdroidApplicationSigned(context: Context, packageName: String, apkFilePath: String, signature: String): Boolean { if (isFdroidApplication(packageName)) { return ApkSignatureManager.verifyFdroidSignature(context, apkFilePath, signature) } return false } - private suspend fun isFdroidApplication(packageName: String): Boolean { + override suspend fun isFdroidApplication(packageName: String): Boolean { return fdroidApi.getFdroidInfoForPackage(packageName).isSuccessful } } diff --git a/app/src/main/java/foundation/e/apps/api/fdroid/IFdroidRepository.kt b/app/src/main/java/foundation/e/apps/api/fdroid/IFdroidRepository.kt new file mode 100644 index 000000000..2e18be4cf --- /dev/null +++ b/app/src/main/java/foundation/e/apps/api/fdroid/IFdroidRepository.kt @@ -0,0 +1,44 @@ +/* + * 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.api.fdroid + +import android.content.Context +import foundation.e.apps.api.fdroid.models.FdroidEntity +import foundation.e.apps.api.fused.data.FusedApp + +interface IFdroidRepository { + /** + * Get Fdroid entity from DB is present. + * If not present then make an API call, store the fetched result and return the result. + * + * Result may be null. + */ + suspend fun getFdroidInfo(packageName: String): FdroidEntity? + + suspend fun getAuthorName(fusedApp: FusedApp): String + + suspend fun isFdroidApplicationSigned( + context: Context, + packageName: String, + apkFilePath: String, + signature: String + ): Boolean + + suspend fun isFdroidApplication(packageName: String): Boolean +} diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt index 878d0c5f6..84602f129 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt @@ -384,7 +384,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } private fun setupToolbar(view: View) { - val startDestination = findNavController().graph.startDestination + val startDestination = findNavController().graph.startDestinationId if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() diff --git a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt index a6daaca27..63eadb5c6 100644 --- a/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/applicationlist/ApplicationListRVAdapter.kt @@ -281,6 +281,7 @@ class ApplicationListRVAdapter( Status.INSTALLATION_ISSUE -> { handleInstallationIssue(view, searchApp) } + else -> {} } } diff --git a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt index 0508db1eb..f527d7a7a 100644 --- a/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt +++ b/app/src/main/java/foundation/e/apps/di/RepositoryModule.kt @@ -6,6 +6,10 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import foundation.e.apps.api.exodus.repositories.AppPrivacyInfoRepositoryImpl import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository +import foundation.e.apps.api.fdroid.FdroidRepository +import foundation.e.apps.api.fdroid.IFdroidRepository +import foundation.e.apps.manager.fused.FusedManagerImpl +import foundation.e.apps.manager.fused.IFusedManager import javax.inject.Singleton @Module @@ -14,4 +18,12 @@ interface RepositoryModule { @Singleton @Binds fun getRepositoryModule(trackerRepositoryImpl: AppPrivacyInfoRepositoryImpl): IAppPrivacyInfoRepository + + @Singleton + @Binds + fun getFusedManagerImpl(fusedManagerImpl: FusedManagerImpl): IFusedManager + + @Singleton + @Binds + fun getFdroidRepository(fusedManagerImpl: FdroidRepository): IFdroidRepository } diff --git a/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt b/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt index 5eecedc3c..858546a7b 100644 --- a/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/home/model/HomeChildRVAdapter.kt @@ -124,6 +124,7 @@ class HomeChildRVAdapter( Status.INSTALLATION_ISSUE -> { handleInstallationIssue(view, homeApp) } + else -> {} } } } 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 a9ce748df..d5f8ad085 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 @@ -57,42 +57,42 @@ class FusedManagerImpl @Inject constructor( @Named("download") private val downloadNotificationChannel: NotificationChannel, @Named("update") private val updateNotificationChannel: NotificationChannel, @ApplicationContext private val context: Context -) { +) : IFusedManager { private val TAG = FusedManagerImpl::class.java.simpleName @RequiresApi(Build.VERSION_CODES.O) - fun createNotificationChannels() { + override fun createNotificationChannels() { notificationManager.apply { createNotificationChannel(downloadNotificationChannel) createNotificationChannel(updateNotificationChannel) } } - suspend fun addDownload(fusedDownload: FusedDownload) { + override suspend fun addDownload(fusedDownload: FusedDownload) { fusedDownload.status = Status.QUEUED databaseRepository.addDownload(fusedDownload) } - suspend fun getDownloadById(fusedDownload: FusedDownload): FusedDownload? { + override suspend fun getDownloadById(fusedDownload: FusedDownload): FusedDownload? { return databaseRepository.getDownloadById(fusedDownload.id) } - suspend fun getDownloadList(): List { + override suspend fun getDownloadList(): List { return databaseRepository.getDownloadList() } - fun getDownloadLiveList(): LiveData> { + override fun getDownloadLiveList(): LiveData> { return databaseRepository.getDownloadLiveList() } - suspend fun clearInstallationIssue(fusedDownload: FusedDownload) { + override suspend fun clearInstallationIssue(fusedDownload: FusedDownload) { flushOldDownload(fusedDownload.packageName) databaseRepository.deleteDownload(fusedDownload) } @OptIn(DelicateCoroutinesApi::class) - suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) { + override suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) { if (status == Status.INSTALLED) { fusedDownload.status = status DownloadManagerBR.downloadedList.clear() @@ -108,7 +108,7 @@ class FusedManagerImpl @Inject constructor( private val mutex = Mutex() - suspend fun downloadApp(fusedDownload: FusedDownload) { + override suspend fun downloadApp(fusedDownload: FusedDownload) { mutex.withLock { when (fusedDownload.type) { Type.NATIVE -> downloadNativeApp(fusedDownload) @@ -117,7 +117,7 @@ class FusedManagerImpl @Inject constructor( } } - suspend fun installApp(fusedDownload: FusedDownload) { + override suspend fun installApp(fusedDownload: FusedDownload) { val list = mutableListOf() when (fusedDownload.type) { Type.NATIVE -> { @@ -146,7 +146,7 @@ class FusedManagerImpl @Inject constructor( } @OptIn(DelicateCoroutinesApi::class) - suspend fun cancelDownload(fusedDownload: FusedDownload) { + override suspend fun cancelDownload(fusedDownload: FusedDownload) { if (fusedDownload.id.isNotBlank()) { fusedDownload.downloadIdMap.forEach { (key, _) -> downloadManager.remove(key) @@ -164,7 +164,7 @@ class FusedManagerImpl @Inject constructor( } } - suspend fun getFusedDownload(downloadId: Long = 0, packageName: String = ""): FusedDownload { + override suspend fun getFusedDownload(downloadId: Long, packageName: String): FusedDownload { val downloadList = getDownloadList() var fusedDownload = FusedDownload() downloadList.forEach { @@ -181,12 +181,12 @@ class FusedManagerImpl @Inject constructor( return fusedDownload } - private fun flushOldDownload(packageName: String) { + override fun flushOldDownload(packageName: String) { val parentPathFile = File("$cacheDir/$packageName") if (parentPathFile.exists()) parentPathFile.deleteRecursively() } - private suspend fun downloadNativeApp(fusedDownload: FusedDownload) { + override suspend fun downloadNativeApp(fusedDownload: FusedDownload) { var count = 0 var parentPath = "$cacheDir/${fusedDownload.packageName}" @@ -214,7 +214,7 @@ class FusedManagerImpl @Inject constructor( databaseRepository.updateDownload(fusedDownload) } - private fun getGplayInstallationPackagePath( + override fun getGplayInstallationPackagePath( fusedDownload: FusedDownload, it: String, parentPath: String, @@ -228,7 +228,7 @@ class FusedManagerImpl @Inject constructor( } } - private fun createObbFileForDownload( + override fun createObbFileForDownload( fusedDownload: FusedDownload, url: String ): File { @@ -239,7 +239,7 @@ class FusedManagerImpl @Inject constructor( return File(parentPath, obbFile.name) } - fun moveOBBFilesToOBBDirectory(fusedDownload: FusedDownload) { + override fun moveOBBFilesToOBBDirectory(fusedDownload: FusedDownload) { fusedDownload.files.forEach { val parentPath = context.getExternalFilesDir(null)?.absolutePath + "/Android/obb/" + fusedDownload.packageName @@ -253,39 +253,39 @@ class FusedManagerImpl @Inject constructor( } } - fun getBaseApkPath(fusedDownload: FusedDownload) = + override fun getBaseApkPath(fusedDownload: FusedDownload) = "$cacheDir/${fusedDownload.packageName}/${fusedDownload.packageName}_1.apk" - suspend fun installationIssue(fusedDownload: FusedDownload) { + override suspend fun installationIssue(fusedDownload: FusedDownload) { flushOldDownload(fusedDownload.packageName) fusedDownload.status = Status.INSTALLATION_ISSUE databaseRepository.updateDownload(fusedDownload) } - suspend fun updateAwaiting(fusedDownload: FusedDownload) { + override suspend fun updateAwaiting(fusedDownload: FusedDownload) { fusedDownload.status = Status.AWAITING databaseRepository.updateDownload(fusedDownload) } - suspend fun updateUnavailable(fusedDownload: FusedDownload) { + override suspend fun updateUnavailable(fusedDownload: FusedDownload) { fusedDownload.status = Status.UNAVAILABLE databaseRepository.updateDownload(fusedDownload) } - suspend fun updateFusedDownload(fusedDownload: FusedDownload) { + override suspend fun updateFusedDownload(fusedDownload: FusedDownload) { databaseRepository.updateDownload(fusedDownload) } - suspend fun insertFusedDownloadPurchaseNeeded(fusedDownload: FusedDownload) { + override suspend fun insertFusedDownloadPurchaseNeeded(fusedDownload: FusedDownload) { fusedDownload.status = Status.PURCHASE_NEEDED databaseRepository.addDownload(fusedDownload) } - fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean { + override fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean { return pkgManagerModule.isInstalled(fusedDownload.packageName) } - fun getFusedDownloadInstallationStatus(fusedApp: FusedDownload): Status { + override 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 c9bd1d6dd..e59041073 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 @@ -5,6 +5,7 @@ import android.os.Build import androidx.annotation.RequiresApi import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow +import foundation.e.apps.OpenForTesting import foundation.e.apps.api.fdroid.FdroidRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.database.fusedDownload.FusedDownload @@ -16,8 +17,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton +@OpenForTesting class FusedManagerRepository @Inject constructor( - private val fusedManagerImpl: FusedManagerImpl, + private val fusedManagerImpl: IFusedManager, private val fdroidRepository: FdroidRepository ) { diff --git a/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt b/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt new file mode 100644 index 000000000..f0d3891d6 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt @@ -0,0 +1,82 @@ +/* + * 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.fused + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.lifecycle.LiveData +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.utils.enums.Status +import kotlinx.coroutines.DelicateCoroutinesApi +import java.io.File + +interface IFusedManager { + @RequiresApi(Build.VERSION_CODES.O) + fun createNotificationChannels() + + suspend fun addDownload(fusedDownload: FusedDownload) + + suspend fun getDownloadById(fusedDownload: FusedDownload): FusedDownload? + + suspend fun getDownloadList(): List + fun getDownloadLiveList(): LiveData> + + suspend fun clearInstallationIssue(fusedDownload: FusedDownload) + + @OptIn(DelicateCoroutinesApi::class) + suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) + + suspend fun downloadApp(fusedDownload: FusedDownload) + + suspend fun installApp(fusedDownload: FusedDownload) + + @OptIn(DelicateCoroutinesApi::class) + suspend fun cancelDownload(fusedDownload: FusedDownload) + + suspend fun getFusedDownload(downloadId: Long = 0, packageName: String = ""): FusedDownload + fun flushOldDownload(packageName: String) + + suspend fun downloadNativeApp(fusedDownload: FusedDownload) + fun getGplayInstallationPackagePath( + fusedDownload: FusedDownload, + it: String, + parentPath: String, + count: Int + ): File + + fun createObbFileForDownload( + fusedDownload: FusedDownload, + url: String + ): File + + fun moveOBBFilesToOBBDirectory(fusedDownload: FusedDownload) + fun getBaseApkPath(fusedDownload: FusedDownload): String + + suspend fun installationIssue(fusedDownload: FusedDownload) + + suspend fun updateAwaiting(fusedDownload: FusedDownload) + + suspend fun updateUnavailable(fusedDownload: FusedDownload) + + suspend fun updateFusedDownload(fusedDownload: FusedDownload) + + suspend fun insertFusedDownloadPurchaseNeeded(fusedDownload: FusedDownload) + fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean + fun getFusedDownloadInstallationStatus(fusedApp: FusedDownload): 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 b0c5b749b..b4bcff8e4 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 @@ -19,7 +19,6 @@ 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 @@ -86,6 +85,12 @@ class AppInstallProcessor @Inject constructor( return@let } + if (!fusedManagerRepository.validateFusedDownload(fusedDownload)) { + fusedManagerRepository.installationIssue(it) + Timber.d("!!! installationIssue") + return@let + } + if (fusedDownload.areFilesDownloaded() && !fusedManagerRepository.isFusedDownloadInstalled( fusedDownload ) @@ -96,12 +101,6 @@ class AppInstallProcessor @Inject constructor( runInForeground?.invoke(it.name) - if (!fusedManagerRepository.validateFusedDownload(fusedDownload)) { - fusedManagerRepository.installationIssue(it) - Timber.d("!!! installationIssue") - return@let - } - startAppInstallationProcess(it) mutex.lock() } @@ -204,7 +203,8 @@ class AppInstallProcessor @Inject constructor( try { handleFusedDownloadStatus(download) } catch (e: Exception) { - Log.e(TAG, "observeDownload: ", e) + Timber.e(TAG, "observeDownload: ", e) + fusedManagerRepository.installationIssue(download) finishInstallation(download) } } 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 601f7c299..c7d5b3b9b 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 @@ -14,7 +14,6 @@ import androidx.work.CoroutineWorker import androidx.work.WorkInfo.State import androidx.work.WorkManager import androidx.work.WorkerParameters -import androidx.work.await import com.aurora.gplayapi.data.models.AuthData import com.google.gson.Gson import dagger.assisted.Assisted @@ -35,10 +34,12 @@ import foundation.e.apps.utils.enums.User import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.modules.DataStoreManager -import kotlinx.coroutines.delay -import timber.log.Timber import java.io.ByteArrayOutputStream import java.net.URL +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import timber.log.Timber @HiltWorker class UpdatesWorker @AssistedInject constructor( @@ -85,7 +86,10 @@ class UpdatesWorker @AssistedInject constructor( private suspend fun checkManualUpdateRunning(): Boolean { val workInfos = - WorkManager.getInstance(context).getWorkInfosByTag(UpdatesWorkManager.USER_TAG).await() + withContext(Dispatchers.IO) { + WorkManager.getInstance(context).getWorkInfosByTag(UpdatesWorkManager.USER_TAG) + .get() + } if (workInfos.isNotEmpty()) { val workInfo = workInfos[0] Timber.d("Manual update status: workInfo.state=${workInfo.state}, id=${workInfo.id}") diff --git a/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt b/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt index e3842065c..8f03f4ecb 100644 --- a/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt +++ b/app/src/test/java/foundation/e/apps/FusedApiRepositoryTest.kt @@ -30,24 +30,24 @@ import org.mockito.kotlin.any class FusedApiRepositoryTest { private lateinit var fusedApiRepository: FusedAPIRepository @Mock - private lateinit var fusedApiImple: FusedAPIImpl + private lateinit var fusedAPIImpl: FusedAPIImpl @Before fun setup() { MockitoAnnotations.openMocks(this) - fusedApiRepository = FusedAPIRepository(fusedApiImple) + fusedApiRepository = FusedAPIRepository(fusedAPIImpl) } @Test fun isAnyAppUpdated_ReturnsTrue() { - Mockito.`when`(fusedApiImple.isAnyFusedAppUpdated(any(), any())).thenReturn(true) + Mockito.`when`(fusedAPIImpl.isAnyFusedAppUpdated(any(), any())).thenReturn(true) val isAnyAppUpdated = fusedApiRepository.isAnyFusedAppUpdated(listOf(), listOf()) assertTrue("isAnyAppUpdated", isAnyAppUpdated) } @Test fun isAnyInstallStatusChanged_ReturnsTrue() { - Mockito.`when`(fusedApiImple.isAnyAppInstallStatusChanged(any())).thenReturn(true) + Mockito.`when`(fusedAPIImpl.isAnyAppInstallStatusChanged(any())).thenReturn(true) val isAnyAppUpdated = fusedApiRepository.isAnyAppInstallStatusChanged(listOf()) assertTrue("isAnyAppUpdated", isAnyAppUpdated) } diff --git a/app/src/test/java/foundation/e/apps/fusedManager/FakeFusedManager.kt b/app/src/test/java/foundation/e/apps/fusedManager/FakeFusedManager.kt new file mode 100644 index 000000000..bfe765b2e --- /dev/null +++ b/app/src/test/java/foundation/e/apps/fusedManager/FakeFusedManager.kt @@ -0,0 +1,130 @@ +/* + * 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.fusedManager + +import androidx.lifecycle.LiveData +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.manager.database.fusedDownload.FusedDownloadDAO +import foundation.e.apps.manager.fused.IFusedManager +import foundation.e.apps.utils.enums.Status +import java.io.File + +class FakeFusedManager(private val fusedDownloadDAO: FusedDownloadDAO) : IFusedManager { + override fun createNotificationChannels() { + TODO("Not yet implemented") + } + + override suspend fun addDownload(fusedDownload: FusedDownload) { + fusedDownload.status = Status.QUEUED + fusedDownloadDAO.addDownload(fusedDownload) + } + + override suspend fun getDownloadById(fusedDownload: FusedDownload): FusedDownload? { + return fusedDownloadDAO.getDownloadById(fusedDownload.id) + } + + override suspend fun getDownloadList(): List { + TODO("Not yet implemented") + } + + override fun getDownloadLiveList(): LiveData> { + TODO("Not yet implemented") + } + + override suspend fun clearInstallationIssue(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) { + TODO("Not yet implemented") + } + + override suspend fun downloadApp(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun installApp(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun cancelDownload(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun getFusedDownload(downloadId: Long, packageName: String): FusedDownload { + TODO("Not yet implemented") + } + + override fun flushOldDownload(packageName: String) { + TODO("Not yet implemented") + } + + override suspend fun downloadNativeApp(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override fun getGplayInstallationPackagePath( + fusedDownload: FusedDownload, + it: String, + parentPath: String, + count: Int + ): File { + TODO("Not yet implemented") + } + + override fun createObbFileForDownload(fusedDownload: FusedDownload, url: String): File { + TODO("Not yet implemented") + } + + override fun moveOBBFilesToOBBDirectory(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override fun getBaseApkPath(fusedDownload: FusedDownload): String { + return "root/data/apps/${fusedDownload.packageName}/${fusedDownload.packageName}_1.apk" + } + + override suspend fun installationIssue(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun updateAwaiting(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun updateUnavailable(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun updateFusedDownload(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override suspend fun insertFusedDownloadPurchaseNeeded(fusedDownload: FusedDownload) { + TODO("Not yet implemented") + } + + override fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean { + TODO("Not yet implemented") + } + + override fun getFusedDownloadInstallationStatus(fusedApp: FusedDownload): Status { + TODO("Not yet implemented") + } +} diff --git a/app/src/test/java/foundation/e/apps/fusedManager/FusedManagerRepositoryTest.kt b/app/src/test/java/foundation/e/apps/fusedManager/FusedManagerRepositoryTest.kt new file mode 100644 index 000000000..801c45a32 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/fusedManager/FusedManagerRepositoryTest.kt @@ -0,0 +1,126 @@ +/* + * 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.fusedManager + +import android.app.Application +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import foundation.e.apps.api.fdroid.FdroidRepository +import foundation.e.apps.installProcessor.FakeFusedDownloadDAO +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.manager.database.fusedDownload.FusedDownloadDAO +import foundation.e.apps.manager.fused.FusedManagerRepository +import foundation.e.apps.manager.workmanager.InstallWorkManager +import foundation.e.apps.util.MainCoroutineRule +import foundation.e.apps.utils.enums.Status +import io.mockk.every +import io.mockk.mockkObject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +class FusedManagerRepositoryTest { + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + // Sets the main coroutines dispatcher to a TestCoroutineScope for unit testing. + @ExperimentalCoroutinesApi + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + private lateinit var fusedDownloadDAO: FusedDownloadDAO + private lateinit var fakeFusedManager: FakeFusedManager + + @Mock + private lateinit var application: Application + + @Mock + private lateinit var fdroidRepository: FdroidRepository + + private lateinit var fusedManagerRepository: FusedManagerRepository + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + InstallWorkManager.context = application + fusedDownloadDAO = FakeFusedDownloadDAO() + fakeFusedManager = FakeFusedManager(fusedDownloadDAO) + fusedManagerRepository = FusedManagerRepository(fakeFusedManager, fdroidRepository) + } + + @Test + fun addDownload() = runTest { + val fusedDownload = initTest() + + val isSuccessful = fusedManagerRepository.addDownload(fusedDownload) + assertTrue("addDownload", isSuccessful) + assertEquals("addDownload", 1, fusedDownloadDAO.getDownloadList().size) + } + + private fun initTest(hasAnyExistingWork: Boolean = false): FusedDownload { + mockkObject(InstallWorkManager) + every { InstallWorkManager.checkWorkIsAlreadyAvailable(any()) } returns hasAnyExistingWork + return createFusedDownload() + } + + @Test + fun `addDownload when work is already available`() = runTest { + val fusedDownload = initTest(true) + + val isSuccessful = fusedManagerRepository.addDownload(fusedDownload) + assertFalse("addDownload", isSuccessful) + } + + @Test + fun `addDownload when fusedDownload already exists`() = runTest { + val fusedDownload = initTest() + fusedDownloadDAO.addDownload(fusedDownload) + + val isSuccessful = fusedManagerRepository.addDownload(fusedDownload) + assertFalse("addDownload", isSuccessful) + } + + @Test + fun `addDownload when fusedDownload already exists And has installation issue`() = runTest { + val fusedDownload = initTest() + fusedDownload.status = Status.INSTALLATION_ISSUE + fusedDownloadDAO.addDownload(fusedDownload) + + val isSuccessful = fusedManagerRepository.addDownload(fusedDownload) + assertTrue("addDownload", isSuccessful) + } + + private fun createFusedDownload( + packageName: String? = null, + downloadUrlList: MutableList? = null + ) = FusedDownload( + id = "121", + status = Status.AWAITING, + downloadURLList = downloadUrlList ?: mutableListOf("apk1", "apk2"), + packageName = packageName ?: "com.unit.test" + ) +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt new file mode 100644 index 000000000..5156a7b34 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/AppInstallProcessorTest.kt @@ -0,0 +1,195 @@ +/* + * 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.installProcessor + +import android.app.DownloadManager.Query +import android.content.Context +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.api.DownloadManager +import foundation.e.apps.api.fdroid.FdroidRepository +import foundation.e.apps.manager.database.DatabaseRepository +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.manager.fused.IFusedManager +import foundation.e.apps.manager.workmanager.AppInstallProcessor +import foundation.e.apps.util.MainCoroutineRule +import foundation.e.apps.utils.enums.Status +import foundation.e.apps.utils.modules.DataStoreManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +class AppInstallProcessorTest { + // Run tasks synchronously + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + // Sets the main coroutines dispatcher to a TestCoroutineScope for unit testing. + @ExperimentalCoroutinesApi + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + private lateinit var fakeFusedDownloadDAO: FakeFusedDownloadDAO + private lateinit var databaseRepository: DatabaseRepository + private lateinit var fakeDownloadManager: DownloadManager + private lateinit var fakeFusedManagerRepository: FakeFusedManagerRepository + + @Mock + private lateinit var fakeFusedManager: IFusedManager + + @Mock + private lateinit var fakeFdroidRepository: FdroidRepository + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var downloadManager: android.app.DownloadManager + + @Mock + private lateinit var query: Query + + @Mock + private lateinit var dataStoreManager: DataStoreManager + + private lateinit var appInstallProcessor: AppInstallProcessor + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + fakeFusedDownloadDAO = FakeFusedDownloadDAO() + databaseRepository = DatabaseRepository(fakeFusedDownloadDAO) + fakeFusedManagerRepository = + FakeFusedManagerRepository(fakeFusedDownloadDAO, fakeFusedManager, fakeFdroidRepository) + fakeDownloadManager = + FakeDownloadManager(downloadManager, "/home/data/foundation.e.apps/", query) + + appInstallProcessor = AppInstallProcessor( + context, + databaseRepository, + fakeFusedManagerRepository, + fakeDownloadManager, + dataStoreManager + ) + } + + @Test + fun processInstallTest() = runTest { + val fusedDownload = initTest() + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertTrue("processInstall", finalFusedDownload == null) + } + + private suspend fun initTest( + packageName: String? = null, + downloadUrlList: MutableList? = null + ): FusedDownload { + val fusedDownload = createFusedDownload(packageName, downloadUrlList) + fakeFusedDownloadDAO.addDownload(fusedDownload) + Mockito.`when`(dataStoreManager.getAuthData()).thenReturn(AuthData("", "")) + return fusedDownload + } + + @Test + fun `processInstallTest when FusedDownload is already failed`() = runTest { + val fusedDownload = initTest() + fusedDownload.status = Status.BLOCKED + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.BLOCKED, finalFusedDownload?.status) + } + + @Test + fun `processInstallTest when files are downloaded but not installed`() = runTest { + val fusedDownload = initTest() + fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertTrue("processInstall", finalFusedDownload == null) + } + + @Test + fun `processInstallTest when packageName is empty and files are downloaded`() = runTest { + val fusedDownload = initTest(packageName = "") + fusedDownload.downloadIdMap = mutableMapOf(Pair(231, true)) + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun `processInstallTest when downloadUrls are not available`() = runTest { + val fusedDownload = initTest(downloadUrlList = mutableListOf()) + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun `processInstallTest when exception is occurred`() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.forceCrash = true + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun `processInstallTest when download is failed`() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.willDownloadFail = true + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + @Test + fun `processInstallTest when install is failed`() = runTest { + val fusedDownload = initTest() + fakeFusedManagerRepository.willInstallFail = true + + val finalFusedDownload = runProcessInstall(fusedDownload) + assertEquals("processInstall", Status.INSTALLATION_ISSUE, finalFusedDownload?.status) + } + + private suspend fun runProcessInstall(fusedDownload: FusedDownload): FusedDownload? { + appInstallProcessor.processInstall(fusedDownload.id, false) + return fakeFusedDownloadDAO.getDownloadById(fusedDownload.id) + } + + private fun createFusedDownload( + packageName: String? = null, + downloadUrlList: MutableList? = null + ) = FusedDownload( + id = "121", + status = Status.AWAITING, + downloadURLList = downloadUrlList ?: mutableListOf("apk1", "apk2"), + packageName = packageName ?: "com.unit.test" + ) +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeDownloadManager.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeDownloadManager.kt new file mode 100644 index 000000000..58c38f0a9 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeDownloadManager.kt @@ -0,0 +1,37 @@ +/* + * 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.installProcessor + +import foundation.e.apps.api.DownloadManager + +class FakeDownloadManager( + downloadManger: android.app.DownloadManager, + cacheDir: String, + query: android.app.DownloadManager.Query +) : DownloadManager(downloadManger, cacheDir, query) { + + override suspend fun checkDownloadProcess( + downloadingIds: LongArray, + handleFailed: suspend () -> Unit + ) { + if (downloadingIds.contains(-1)) { + handleFailed() + } + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedDownloadDAO.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedDownloadDAO.kt new file mode 100644 index 000000000..3ec0dcc8c --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedDownloadDAO.kt @@ -0,0 +1,55 @@ +/* + * 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.installProcessor + +import androidx.lifecycle.LiveData +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.manager.database.fusedDownload.FusedDownloadDAO + +class FakeFusedDownloadDAO : FusedDownloadDAO { + val fusedDownloadList = mutableListOf() + + override suspend fun addDownload(fusedDownload: FusedDownload) { + fusedDownloadList.add(fusedDownload) + } + + override fun getDownloadLiveList(): LiveData> { + TODO("Not yet implemented") + } + + override suspend fun getDownloadList(): List { + return fusedDownloadList + } + + override suspend fun getDownloadById(id: String): FusedDownload? { + return fusedDownloadList.find { it.id == id } + } + + override fun getDownloadFlowById(id: String): LiveData { + TODO("Not yet implemented") + } + + override suspend fun updateDownload(fusedDownload: FusedDownload) { + fusedDownloadList.replaceAll { if (it.id == fusedDownload.id) fusedDownload else it } + } + + override suspend fun deleteDownload(fusedDownload: FusedDownload) { + fusedDownloadList.remove(fusedDownload) + } +} diff --git a/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedManagerRepository.kt b/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedManagerRepository.kt new file mode 100644 index 000000000..04925cf6a --- /dev/null +++ b/app/src/test/java/foundation/e/apps/installProcessor/FakeFusedManagerRepository.kt @@ -0,0 +1,103 @@ +/* + * 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.installProcessor + +import foundation.e.apps.api.fdroid.FdroidRepository +import foundation.e.apps.manager.database.fusedDownload.FusedDownload +import foundation.e.apps.manager.fused.FusedManagerRepository +import foundation.e.apps.manager.fused.IFusedManager +import foundation.e.apps.utils.enums.Status +import kotlinx.coroutines.delay + +class FakeFusedManagerRepository( + private val fusedDownloadDAO: FakeFusedDownloadDAO, + fusedManager: IFusedManager, + fdroidRepository: FdroidRepository, +) : FusedManagerRepository(fusedManager, fdroidRepository) { + var isAppInstalled = false + var installationStatus = Status.INSTALLED + var willDownloadFail = false + var willInstallFail = false + var forceCrash = false + + override suspend fun downloadApp(fusedDownload: FusedDownload) { + fusedDownload.status = Status.DOWNLOADING + fusedDownload.downloadIdMap = mutableMapOf(Pair(341, false), Pair(342, false)) + fusedDownloadDAO.updateDownload(fusedDownload) + delay(10000) + + if (willDownloadFail) { + fusedDownload.downloadIdMap.clear() + fusedDownload.downloadIdMap = mutableMapOf(Pair(-1, false), Pair(-1, false)) + fusedDownloadDAO.updateDownload(fusedDownload) + return + } + + fusedDownload.downloadIdMap.replaceAll { _, _ -> true } + fusedDownload.status = Status.DOWNLOADED + fusedDownloadDAO.updateDownload(fusedDownload) + } + + override suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) { + when (status) { + Status.INSTALLING -> { + handleStatusInstalling(fusedDownload) + + } + Status.INSTALLED -> { + if (forceCrash) { + throw RuntimeException() + } + + fusedDownloadDAO.deleteDownload(fusedDownload) + } + else -> { + fusedDownload.status = status + fusedDownloadDAO.updateDownload(fusedDownload) + } + } + } + + private suspend fun handleStatusInstalling( + fusedDownload: FusedDownload + ) { + fusedDownload.status = Status.INSTALLING + fusedDownloadDAO.updateDownload(fusedDownload) + delay(5000) + + if (willInstallFail) { + updateDownloadStatus(fusedDownload, Status.INSTALLATION_ISSUE) + } else { + updateDownloadStatus(fusedDownload, Status.INSTALLED) + } + } + + override suspend fun installationIssue(fusedDownload: FusedDownload) { + fusedDownload.status = Status.INSTALLATION_ISSUE + fusedDownloadDAO.updateDownload(fusedDownload) + } + + override fun isFusedDownloadInstalled(fusedDownload: FusedDownload): Boolean { + return isAppInstalled + } + + override fun getFusedDownloadPackageStatus(fusedDownload: FusedDownload): Status { + return installationStatus + } +} diff --git a/build.gradle b/build.gradle index 474556714..04825ecdd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.1.0' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' - classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" - classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3" + classpath "com.google.dagger:hilt-android-gradle-plugin:2.44.2" classpath "org.jetbrains.kotlin:kotlin-allopen:1.6.10" // NOTE: Do not place your application dependencies here; they belong -- GitLab From 4d2cd63c6851a38cfe6b67a0eead4c9ac0f5c891 Mon Sep 17 00:00:00 2001 From: hasibprince Date: Mon, 13 Feb 2023 16:27:40 +0600 Subject: [PATCH 2/2] dependenices are reversed to previous versions --- app/build.gradle | 8 ++++---- .../foundation/e/apps/application/ApplicationFragment.kt | 2 +- .../java/foundation/e/apps/manager/fused/IFusedManager.kt | 3 --- build.gradle | 6 +++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 03a39e300..35ed7284f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -157,7 +157,7 @@ dependencies { testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation "androidx.arch.core:core-testing:2.1.0" - testImplementation "io.mockk:mockk:1.13.4" + testImplementation "io.mockk:mockk:1.12.3" // Coil and PhotoView implementation "io.coil-kt:coil:1.4.0" @@ -193,19 +193,19 @@ dependencies { implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2" // Navigation Components - def navigation_version = "2.5.3" + def navigation_version = "2.3.5" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" // Hilt - def hilt_version = '2.44.2' + def hilt_version = '2.40.5' kapt "com.google.dagger:hilt-compiler:$hilt_version" implementation "com.google.dagger:hilt-android:$hilt_version" implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0' // Lifecycle Components - def lifecycle_version = "2.5.1" + def lifecycle_version = "2.4.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" implementation "android.arch.lifecycle:extensions:1.1.1" diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt index 84602f129..878d0c5f6 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt @@ -384,7 +384,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } private fun setupToolbar(view: View) { - val startDestination = findNavController().graph.startDestinationId + val startDestination = findNavController().graph.startDestination if (startDestination == R.id.applicationFragment) { binding.toolbar.setNavigationOnClickListener { val action = ApplicationFragmentDirections.actionApplicationFragmentToHomeFragment() diff --git a/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt b/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt index f0d3891d6..94ab9705e 100644 --- a/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt +++ b/app/src/main/java/foundation/e/apps/manager/fused/IFusedManager.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi import java.io.File interface IFusedManager { - @RequiresApi(Build.VERSION_CODES.O) fun createNotificationChannels() suspend fun addDownload(fusedDownload: FusedDownload) @@ -39,14 +38,12 @@ interface IFusedManager { suspend fun clearInstallationIssue(fusedDownload: FusedDownload) - @OptIn(DelicateCoroutinesApi::class) suspend fun updateDownloadStatus(fusedDownload: FusedDownload, status: Status) suspend fun downloadApp(fusedDownload: FusedDownload) suspend fun installApp(fusedDownload: FusedDownload) - @OptIn(DelicateCoroutinesApi::class) suspend fun cancelDownload(fusedDownload: FusedDownload) suspend fun getFusedDownload(downloadId: Long = 0, packageName: String = ""): FusedDownload diff --git a/build.gradle b/build.gradle index 04825ecdd..474556714 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.1.0' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.21' - classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3" - classpath "com.google.dagger:hilt-android-gradle-plugin:2.44.2" + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10' + classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" + classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5" classpath "org.jetbrains.kotlin:kotlin-allopen:1.6.10" // NOTE: Do not place your application dependencies here; they belong -- GitLab