diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt index 3364511b078b858d73799e3cb7cd02ec44186476..2630550acfd221e3cccb254334bc197ffa65515d 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt @@ -114,7 +114,11 @@ class ApplicationDataManager @Inject constructor( return if (application.is_pwa) { pwaManager.getPwaStatus(application) } else { - appLoungePackageManager.getPackageStatus(application.package_name, application.latest_version_code) + appLoungePackageManager.getPackageStatus( + application.package_name, + application.latest_version_code, + application.latest_version_number, + ) } } } diff --git a/app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt b/app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt index 25d9487da644fff2e6420c06914223fad997bc7f..da2ecfd2b54ac54c17e7c475ef8a1ebb36d3f330 100644 --- a/app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt @@ -98,7 +98,7 @@ class SystemAppsUpdatesRepository @Inject constructor( Timber.e("Blocked system app: $packageName, details: $systemAppInfo") null } else { - systemAppInfo.toApplication() + systemAppInfo.toApplication(context) } } diff --git a/app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppInfo.kt b/app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppInfo.kt index 5ccb2239a82a5e49d018b922942de4f3e8764c1d..7361cfb644a958c53aa04fd32bd80da9ed037e0b 100644 --- a/app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppInfo.kt +++ b/app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppInfo.kt @@ -17,6 +17,8 @@ package foundation.e.apps.data.gitlab.models +import android.content.Context +import android.text.format.Formatter import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.squareup.moshi.Json import foundation.e.apps.data.application.data.Application @@ -41,7 +43,8 @@ data class SystemAppInfo( private const val RANDOM_SIZE = 1L -fun SystemAppInfo.toApplication(): Application { +fun SystemAppInfo.toApplication(context: Context): Application { + val apkSize = size ?: RANDOM_SIZE return Application( _id = UUID.randomUUID().toString(), author = authorName ?: "eFoundation", @@ -51,7 +54,8 @@ fun SystemAppInfo.toApplication(): Application { name = name, package_name = packageName, origin = Origin.GITLAB_RELEASES, - originalSize = size ?: RANDOM_SIZE, + originalSize = apkSize, + appSize = Formatter.formatFileSize(context, apkSize), url = downloadUrl, isSystemApp = true, filterLevel = FilterLevel.NONE, diff --git a/app/src/main/java/foundation/e/apps/install/pkg/AppLoungePackageManager.kt b/app/src/main/java/foundation/e/apps/install/pkg/AppLoungePackageManager.kt index 78da25fa5847c304065ce155b9070083a8b534e1..edb939a16e5a6a625f53fa63720dc8518047cb6c 100644 --- a/app/src/main/java/foundation/e/apps/install/pkg/AppLoungePackageManager.kt +++ b/app/src/main/java/foundation/e/apps/install/pkg/AppLoungePackageManager.kt @@ -32,17 +32,16 @@ import android.os.Build import androidx.core.content.pm.PackageInfoCompat import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.OpenForTesting +import foundation.e.apps.data.application.search.SearchApi import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.enums.Status import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.application.search.SearchApi import foundation.e.apps.data.install.models.AppInstall -import kotlinx.coroutines.DelicateCoroutinesApi -import timber.log.Timber import java.io.File -import java.lang.IllegalArgumentException import javax.inject.Inject import javax.inject.Singleton +import kotlinx.coroutines.DelicateCoroutinesApi +import timber.log.Timber @Singleton @OpenForTesting @@ -72,9 +71,16 @@ class AppLoungePackageManager @Inject constructor( } } - private fun isUpdatable(packageName: String, versionCode: Int): Boolean { + private fun isUpdatable(packageName: String, versionCode: Int, versionName: String): Boolean { val packageInfo = getPackageInfo(packageName) ?: return false - return versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo) + val installedVersionNumber = PackageInfoCompat.getLongVersionCode(packageInfo) + val installedVersionName = packageInfo.versionName + + val isVersionNumberHigher = versionCode.toLong() > installedVersionNumber + val isVersionNameHigher = + versionName.isNotBlank() && versionName > installedVersionName + + return isVersionNumberHigher || isVersionNameHigher } fun getLaunchIntent(packageName: String): Intent? { @@ -96,9 +102,13 @@ class AppLoungePackageManager @Inject constructor( * * Recommended to use: [SearchApi.getFusedAppInstallationStatus]. */ - fun getPackageStatus(packageName: String, versionCode: Int): Status { + fun getPackageStatus( + packageName: String, + versionCode: Int, + versionName: String = "", + ): Status { return if (isInstalled(packageName)) { - if (isUpdatable(packageName, versionCode)) { + if (isUpdatable(packageName, versionCode, versionName)) { Status.UPDATABLE } else { Status.INSTALLED diff --git a/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt b/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt index f86f01f6fbe39c229ea44ebe6a3834aa7978c4da..861cacf8e20b41e1ef452be57dc613013ad24aad 100644 --- a/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt +++ b/app/src/test/java/foundation/e/apps/apps/AppsApiTest.kt @@ -229,7 +229,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demoone"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -238,7 +239,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demotwo"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -247,7 +249,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demothree"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -287,7 +290,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demoone"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -296,7 +300,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demotwo"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -305,7 +310,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demothree"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -345,7 +351,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demoone"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -354,7 +361,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demotwo"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( @@ -363,7 +371,8 @@ class AppsApiTest { Mockito.`when`( appLoungePackageManager.getPackageStatus( eq("foundation.e.demothree"), - eq(123) + eq(123), + eq(""), ) ) .thenReturn( diff --git a/app/src/test/java/foundation/e/apps/fused/SearchApiImplTest.kt b/app/src/test/java/foundation/e/apps/fused/SearchApiImplTest.kt index ffceefeccbf0c02681c873eebc04dd698beebf8b..117b5cc9fd7c071bb26aa42c9d9170805cb081c6 100644 --- a/app/src/test/java/foundation/e/apps/fused/SearchApiImplTest.kt +++ b/app/src/test/java/foundation/e/apps/fused/SearchApiImplTest.kt @@ -201,7 +201,7 @@ class SearchApiImplTest { willThrowException: Boolean = false ) { Mockito.`when`(pwaManager.getPwaStatus(any())).thenReturn(Status.UNAVAILABLE) - Mockito.`when`(appLoungePackageManager.getPackageStatus(any(), any())) + Mockito.`when`(appLoungePackageManager.getPackageStatus(any(), any(), any())) .thenReturn(Status.UNAVAILABLE) Mockito.`when`( cleanApkAppsRepository.getSearchResult( diff --git a/app/src/test/java/foundation/e/apps/home/HomeApiTest.kt b/app/src/test/java/foundation/e/apps/home/HomeApiTest.kt index 82e5595bf55d4fd7c9d3e2d79edbf3d0c2b7aa3c..34ad19888888cc14d79647d680cb97885a38588e 100644 --- a/app/src/test/java/foundation/e/apps/home/HomeApiTest.kt +++ b/app/src/test/java/foundation/e/apps/home/HomeApiTest.kt @@ -132,7 +132,7 @@ class HomeApiTest { any() ) ).thenReturn(listOf()) - Mockito.`when`(appLoungePackageManager.getPackageStatus(any(), any())) + Mockito.`when`(appLoungePackageManager.getPackageStatus(any(), any(), any())) .thenReturn(Status.UNAVAILABLE) var hasLimitedDataFound = false diff --git a/app/src/test/java/foundation/e/apps/install/pkg/AppLoungePackageManagerTest.kt b/app/src/test/java/foundation/e/apps/install/pkg/AppLoungePackageManagerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f87f2adeb61eea3ba6486677f667d573aa2e8b24 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/install/pkg/AppLoungePackageManagerTest.kt @@ -0,0 +1,261 @@ +/* + * Copyright MURENA SAS 2024 + * 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.install.pkg + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import androidx.core.content.pm.PackageInfoCompat +import foundation.e.apps.data.enums.Status +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +class AppLoungePackageManagerTest { + + private lateinit var appLoungePackageManager: AppLoungePackageManager + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var packageManager: PackageManager + + private val testPackageName = "foundation.e.test" + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + Mockito.`when`(context.packageManager).thenReturn(packageManager) + appLoungePackageManager = AppLoungePackageManager(context) + } + + private fun mockPackagePresence( + expectedPackageName: String, + expectedVersionCode: Int, + expectedVersionName: String, + optionalFlag: Int = 0, + ) { + val expectedPackageInfo = mock(PackageInfo::class.java).apply { + packageName = expectedPackageName + versionName = expectedVersionName + versionCode = expectedVersionCode + } + Mockito.`when`( + packageManager.getPackageInfo(expectedPackageName, optionalFlag) + ).thenReturn(expectedPackageInfo) + } + + private fun mockPackagePresence() { + mockPackagePresence(testPackageName, 0, "") + } + + private fun mockPackageAbsence() { + Mockito.`when`(packageManager.getPackageInfo(testPackageName, PackageManager.GET_META_DATA)) + .thenThrow(PackageManager.NameNotFoundException::class.java) + } + + @Test + fun givenPackageInfoIsPresent_whenCheckIsInstalled_thenReturnTrue() { + mockPackagePresence() + assert(appLoungePackageManager.isInstalled(testPackageName)) + } + + @Test + fun givenPackageInfoIsAbsent_whenCheckIsInstalled_thenReturnFalse() { + mockPackageAbsence() + assertFalse(appLoungePackageManager.isInstalled(testPackageName)) + } + + @Test + fun givenPackageInfoIsPresent_thenReturnProperVersionCode() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + assertEquals( + installedVersionCode.toLong(), + PackageInfoCompat.getLongVersionCode(packageManager.getPackageInfo(testPackageName, 0)) + ) + } + + @Test + fun givenPackageInfoIsAbsent_whenCheckIsUpdatable_thenReturnStatusUNAVAILABLE() { + mockPackageAbsence() + + val newVersionCode = 40903000 + val newVersionName = "4.9.3+20240725" + + assertEquals( + Status.UNAVAILABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + + @Test + fun givenNewVersionCode_andSameVersionName_whenCheckIsUpdatable_thenReturnStatusUPDATABLE() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + val newVersionCode = 40903001 + val newVersionName = "4.9.3" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + + @Test + fun givenNewVersionCode_andNewVersionNameBlank_whenCheckIsUpdatable_thenReturnStatusUPDATABLE() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + val newVersionCode = 40903001 + val newVersionName = "" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + + @Test + fun givenNewVersionName_andSameVersionCode_whenCheckIsUpdatable_thenReturnStatusUPDATABLE() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + val newVersionCode = 40903000 + val newVersionName = "4.9.3+20240725" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + + @Test + fun givenSameVersionCode_andSameVersionName_whenCheckIsUpdatable_thenReturnStatusINSTALLED() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3+20240725" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + val newVersionCode = 40903000 + val newVersionName = "4.9.3+20240725" + + assertEquals( + Status.INSTALLED, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + + @Test + fun givenNewVersionName_whenCheckIsUpdatable_thenReturnStatusUPDATABLE() { + val installedVersionCode = 40903000 + val installedVersionName = "4.9.3+20240725" + + mockPackagePresence( + expectedPackageName = testPackageName, + expectedVersionCode = installedVersionCode, + expectedVersionName = installedVersionName, + ) + + var newVersionCode = 40903000 + var newVersionName = "4.9.3+20240726" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + + newVersionCode = 40903000 + newVersionName = "4.9.3+20240825" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + + newVersionCode = 40903000 + newVersionName = "4.9.3+20250725" + + assertEquals( + Status.UPDATABLE, appLoungePackageManager.getPackageStatus( + packageName = testPackageName, + versionCode = newVersionCode, + versionName = newVersionName, + ) + ) + } + +}