diff --git a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt index aaa672d599590b6aea97ccb951d08a1f8e6f1da4..f8d1968ccaf04d48c69db9a3b1a094f2aaf9684d 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/PlayStoreRepository.kt @@ -46,6 +46,7 @@ import foundation.e.apps.data.application.utils.toApplication import foundation.e.apps.data.enums.Source import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.AuthenticatorRepository +import foundation.e.apps.data.login.PlayStoreAuthenticator import foundation.e.apps.data.playstore.utils.GPlayHttpClient import foundation.e.apps.utils.SystemInfoProvider import kotlinx.coroutines.Dispatchers @@ -144,21 +145,39 @@ class PlayStoreRepository @Inject constructor( return categoryList } - override suspend fun getAppDetails(packageName: String): Application { - var appDetails: GplayApp? + override suspend fun getAppDetails(packageName: String): Application = + withContext(Dispatchers.IO) { + var appDetails: GplayApp + appDetails = getAppDetailsHelper().getAppByPackageName(packageName) - val appDetailsHelper = - AppDetailsHelper(authenticatorRepository.getGPlayAuthOrThrow()).using(gPlayHttpClient) + if (!isEmulator() && appDetails.versionCode == 0) { + // Google Play returns limited result ( i.e. version code being 0) with a stale token, + // so we need to refresh authentication to get a new token. + Timber.i("Version code is 0 for ${appDetails.packageName}.") - withContext(Dispatchers.IO) { - appDetails = appDetailsHelper.getAppByPackageName(packageName) - } + refreshPlayStoreAuthentication() + + appDetails = getAppDetailsHelper().getAppByPackageName(packageName) + + if (appDetails.versionCode == 0) { + Timber.w("After refreshing auth, version code is still 0. Giving up installation.") + throw IllegalStateException("App version code cannot be 0") + } + } - if (!isEmulator() && appDetails?.versionCode == 0) { - throw IllegalStateException("App version code cannot be 0") + appDetails.toApplication(context) } - return appDetails?.toApplication(context) ?: Application() + private fun getAppDetailsHelper(): AppDetailsHelper { + val authData = authenticatorRepository.getGPlayAuthOrThrow() + val appDetailsHelper = AppDetailsHelper(authData).using(gPlayHttpClient) + + return appDetailsHelper + } + + private suspend fun refreshPlayStoreAuthentication() { + Timber.i("Refreshing authentication.") + authenticatorRepository.getAuthObjects(listOf(PlayStoreAuthenticator::class.java.simpleName)) } suspend fun getAppDetailsWeb(packageName: String): Application? { @@ -206,10 +225,7 @@ class PlayStoreRepository @Inject constructor( idOrPackageName: String, versionCode: Int, offerType: Int - ): List { - val downloadData = mutableListOf() - val authData = authenticatorRepository.getGPlayAuthOrThrow() - + ): List = withContext(Dispatchers.IO) { var version = versionCode var offer = offerType @@ -223,11 +239,12 @@ class PlayStoreRepository @Inject constructor( throw IllegalStateException("Could not get download details for $idOrPackageName") } - withContext(Dispatchers.IO) { - val purchaseHelper = PurchaseHelper(authData).using(gPlayHttpClient) - downloadData.addAll(purchaseHelper.purchase(idOrPackageName, version, offer)) - } - return downloadData + // Don't store auth data in a variable. Always get GPlay auth from repository, + // because auth might get refreshed while fetching app details. + val purchaseHelper = PurchaseHelper(authenticatorRepository.getGPlayAuthOrThrow()) + .using(gPlayHttpClient) + + buildList { addAll(purchaseHelper.purchase(idOrPackageName, version, offer)) } } suspend fun getOnDemandModule( diff --git a/app/src/main/java/foundation/e/apps/data/playstore/utils/GPlayHttpClient.kt b/app/src/main/java/foundation/e/apps/data/playstore/utils/GPlayHttpClient.kt index 9ea2d6a1dcb5117e8c98e64a0dc984ec960257fc..d3570cdc377f42323675ac12540a519cb67fd623 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/utils/GPlayHttpClient.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/utils/GPlayHttpClient.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import okhttp3.Cache import okhttp3.Headers.Companion.toHeaders import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl @@ -49,7 +48,6 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class GPlayHttpClient @Inject constructor( - private val cache: Cache, loggingInterceptor: HttpLoggingInterceptor ) : IHttpClient { @@ -77,7 +75,6 @@ class GPlayHttpClient @Inject constructor( .callTimeout(HTTP_TIMEOUT_IN_SECOND, TimeUnit.SECONDS) .followRedirects(true) .followSslRedirects(true) - .cache(cache) .addInterceptor(loggingInterceptor) .build() @@ -212,7 +209,6 @@ class GPlayHttpClient @Inject constructor( } STATUS_CODE_TOO_MANY_REQUESTS -> MainScope().launch { - cache.evictAll() if (url.toString().contains(SEARCH_SUGGEST)) { return@launch } diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index 120a3a0a6edb5011377c45d07bffb341a1747239..4646bf896b67d57fdebe47cb049eb76a3ace136e 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -206,7 +206,7 @@ class AppInstallProcessor @Inject constructor( ) return false } catch (e: IllegalStateException) { - EventBus.invokeEvent(AppEvent.InvalidAuthEvent(AuthObject.GPlayAuth::class.java.simpleName)) + Timber.e(e) } catch (e: Exception) { handleUpdateDownloadError( appInstall, diff --git a/app/src/test/java/foundation/e/apps/gplay/GplyHttpClientTest.kt b/app/src/test/java/foundation/e/apps/gplay/GPlayHttpClientTest.kt similarity index 97% rename from app/src/test/java/foundation/e/apps/gplay/GplyHttpClientTest.kt rename to app/src/test/java/foundation/e/apps/gplay/GPlayHttpClientTest.kt index 3ebcf2e29afbd724fc8fe298c247f0a56d122598..267264a52ca1ec19220ceb87d53e5a895f788ea1 100644 --- a/app/src/test/java/foundation/e/apps/gplay/GplyHttpClientTest.kt +++ b/app/src/test/java/foundation/e/apps/gplay/GPlayHttpClientTest.kt @@ -32,7 +32,6 @@ import io.mockk.mockkObject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest -import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.logging.HttpLoggingInterceptor @@ -48,10 +47,7 @@ import org.mockito.kotlin.any import kotlin.test.assertFailsWith @OptIn(ExperimentalCoroutinesApi::class) -class GplyHttpClientTest { - - @Mock - private lateinit var cache: Cache +class GPlayHttpClientTest { @Mock private lateinit var loggingInterceptor: HttpLoggingInterceptor @@ -70,7 +66,7 @@ class GplyHttpClientTest { @Before fun setup() { MockitoAnnotations.openMocks(this) - gPlayHttpClient = GPlayHttpClient(cache, loggingInterceptor) + gPlayHttpClient = GPlayHttpClient(loggingInterceptor) gPlayHttpClient.okHttpClient = this.okHttpClient call = FakeCall() }