Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit af802c3e authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

Merge branch '3324-retry-app-install-on-expired-token' into 'main'

fix: refresh token before PlayStore app installation when a limited response is returned from GPlay

See merge request !568
parents 52c7f8b2 5ee74ab7
Loading
Loading
Loading
Loading
Loading
+36 −19
Original line number Diff line number Diff line
@@ -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()

        if (!isEmulator() && appDetails?.versionCode == 0) {
                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")
                }
            }

            appDetails.toApplication(context)
        }

    private fun getAppDetailsHelper(): AppDetailsHelper {
        val authData = authenticatorRepository.getGPlayAuthOrThrow()
        val appDetailsHelper = AppDetailsHelper(authData).using(gPlayHttpClient)

        return appDetailsHelper
    }

        return appDetails?.toApplication(context) ?: Application()
    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<File> {
        val downloadData = mutableListOf<File>()
        val authData = authenticatorRepository.getGPlayAuthOrThrow()

    ): List<File> = 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(
+0 −4
Original line number Diff line number Diff line
@@ -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
                    }
+1 −1
Original line number Diff line number Diff line
@@ -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,
+2 −6
Original line number Diff line number Diff line
@@ -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()
    }