Loading app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt +3 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,9 @@ class ApplicationDataManager @Inject constructor( !isRestricted(application) -> FilterLevel.NONE !isApplicationVisible(application) -> FilterLevel.DATA application.originalSize == 0L -> FilterLevel.UI // Restricted with no installable build for this device (e.g. a TV-only app on a // phone, version code 0). Keep it visible but mark the install button N/A. application.latest_version_code <= 0L -> FilterLevel.UI else -> FilterLevel.NONE } } Loading app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +21 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package foundation.e.apps.data.playstore import android.content.Context import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.PlayFile Loading Loading @@ -376,7 +377,13 @@ class PlayStoreRepository @Inject constructor( return appDetails } Timber.i("Version code is 0 for %s.", appDetails.packageName) // A specific restriction code means Google evaluated the request and the app simply // isn't installable for this account/device (e.g. a TV-only app on a phone). Refreshing // auth can't change that, so keep the details we have and let the UI mark it N/A. if (appDetails.isRestrictedFromInstall()) { return appDetails } val refreshedAppDetails = retryAfterSuccessfulPlayAuthRefreshOrNull( operationName = "app details", reason = "version code is 0 for ${appDetails.packageName}", Loading @@ -384,6 +391,9 @@ class PlayStoreRepository @Inject constructor( ) ?: appDetails if (refreshedAppDetails.versionCode == 0L) { if (refreshedAppDetails.isRestrictedFromInstall()) { return refreshedAppDetails } Timber.w("After refreshing auth, version code is still 0. Giving up installation.") throw IllegalStateException("App version code cannot be 0") } Loading @@ -391,6 +401,16 @@ class PlayStoreRepository @Inject constructor( return refreshedAppDetails } /** * True when Google returned a definitive restriction code for this app, meaning it can't be * installed for the current account/device. [Restriction.GENERIC] is excluded because it is * the fallback for absent/unmapped values, which can also mask a stale token; those keep the * auth-refresh recovery path. */ private fun GplayApp.isRestrictedFromInstall(): Boolean { return restriction != Restriction.NOT_RESTRICTED && restriction != Restriction.GENERIC } suspend fun getAppDetailsWeb(packageName: String): Application? { val webAppDetailsHelper = WebAppDetailsHelper().using(gPlayHttpClient) Loading app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt +24 −0 Original line number Diff line number Diff line package foundation.e.apps.data.playstore import androidx.test.core.app.ApplicationProvider import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.AuthData Loading Loading @@ -140,6 +141,28 @@ class PlayStoreRepositoryTest { verify(exactly = 2) { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } } @Test fun `getAppDetails does not refresh token for restricted app with zero version code`() = runTest { val authData = AuthData(email = "user@gmail.com") val restrictedApp = buildAppDetails("pkg.test", 0) every { restrictedApp.restriction } returns Restriction.UNKNOWN val playStoreAuthManager = mock<PlayStoreAuthManager>() val tokenRefreshHandler = FakeTokenRefreshHandler(AuthResult.Success(Unit)) repository = createRepository(playStoreAuthManager, tokenRefreshHandler = tokenRefreshHandler) whenever(playStoreAuthManager.requireValidatedPlayStoreAuth()).thenReturn(authData) every { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } returns restrictedApp val result = repository.getAppDetails("pkg.test") assertThat(result.latest_version_code).isEqualTo(0) assertThat(tokenRefreshHandler.refreshCalls).isEqualTo(0) verify(exactly = 1) { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } } @Test fun `getAppDetails does not retry zero version request when auth refresh fails`() = runTest { val authData = AuthData(email = "user@gmail.com") Loading Loading @@ -684,6 +707,7 @@ class PlayStoreRepositoryTest { every { app.size } returns 1000L every { app.isFree } returns true every { app.price } returns "0" every { app.restriction } returns Restriction.NOT_RESTRICTED return app } Loading Loading
app/src/main/java/foundation/e/apps/data/application/ApplicationDataManager.kt +3 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,9 @@ class ApplicationDataManager @Inject constructor( !isRestricted(application) -> FilterLevel.NONE !isApplicationVisible(application) -> FilterLevel.DATA application.originalSize == 0L -> FilterLevel.UI // Restricted with no installable build for this device (e.g. a TV-only app on a // phone, version code 0). Keep it visible but mark the install button N/A. application.latest_version_code <= 0L -> FilterLevel.UI else -> FilterLevel.NONE } } Loading
app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +21 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package foundation.e.apps.data.playstore import android.content.Context import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.ContentRating import com.aurora.gplayapi.data.models.PlayFile Loading Loading @@ -376,7 +377,13 @@ class PlayStoreRepository @Inject constructor( return appDetails } Timber.i("Version code is 0 for %s.", appDetails.packageName) // A specific restriction code means Google evaluated the request and the app simply // isn't installable for this account/device (e.g. a TV-only app on a phone). Refreshing // auth can't change that, so keep the details we have and let the UI mark it N/A. if (appDetails.isRestrictedFromInstall()) { return appDetails } val refreshedAppDetails = retryAfterSuccessfulPlayAuthRefreshOrNull( operationName = "app details", reason = "version code is 0 for ${appDetails.packageName}", Loading @@ -384,6 +391,9 @@ class PlayStoreRepository @Inject constructor( ) ?: appDetails if (refreshedAppDetails.versionCode == 0L) { if (refreshedAppDetails.isRestrictedFromInstall()) { return refreshedAppDetails } Timber.w("After refreshing auth, version code is still 0. Giving up installation.") throw IllegalStateException("App version code cannot be 0") } Loading @@ -391,6 +401,16 @@ class PlayStoreRepository @Inject constructor( return refreshedAppDetails } /** * True when Google returned a definitive restriction code for this app, meaning it can't be * installed for the current account/device. [Restriction.GENERIC] is excluded because it is * the fallback for absent/unmapped values, which can also mask a stale token; those keep the * auth-refresh recovery path. */ private fun GplayApp.isRestrictedFromInstall(): Boolean { return restriction != Restriction.NOT_RESTRICTED && restriction != Restriction.GENERIC } suspend fun getAppDetailsWeb(packageName: String): Application? { val webAppDetailsHelper = WebAppDetailsHelper().using(gPlayHttpClient) Loading
app/src/test/java/foundation/e/apps/data/playstore/PlayStoreRepositoryTest.kt +24 −0 Original line number Diff line number Diff line package foundation.e.apps.data.playstore import androidx.test.core.app.ApplicationProvider import com.aurora.gplayapi.Constants.Restriction import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork import com.aurora.gplayapi.data.models.AuthData Loading Loading @@ -140,6 +141,28 @@ class PlayStoreRepositoryTest { verify(exactly = 2) { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } } @Test fun `getAppDetails does not refresh token for restricted app with zero version code`() = runTest { val authData = AuthData(email = "user@gmail.com") val restrictedApp = buildAppDetails("pkg.test", 0) every { restrictedApp.restriction } returns Restriction.UNKNOWN val playStoreAuthManager = mock<PlayStoreAuthManager>() val tokenRefreshHandler = FakeTokenRefreshHandler(AuthResult.Success(Unit)) repository = createRepository(playStoreAuthManager, tokenRefreshHandler = tokenRefreshHandler) whenever(playStoreAuthManager.requireValidatedPlayStoreAuth()).thenReturn(authData) every { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } returns restrictedApp val result = repository.getAppDetails("pkg.test") assertThat(result.latest_version_code).isEqualTo(0) assertThat(tokenRefreshHandler.refreshCalls).isEqualTo(0) verify(exactly = 1) { anyConstructed<AppDetailsHelper>().getAppByPackageName("pkg.test") } } @Test fun `getAppDetails does not retry zero version request when auth refresh fails`() = runTest { val authData = AuthData(email = "user@gmail.com") Loading Loading @@ -684,6 +707,7 @@ class PlayStoreRepositoryTest { every { app.size } returns 1000L every { app.isFree } returns true every { app.price } returns "0" every { app.restriction } returns Restriction.NOT_RESTRICTED return app } Loading