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

Commit dd09b375 authored by Hasib Prince's avatar Hasib Prince
Browse files

App Lounge: resolved conflicts: merging main -> 258-blocked_apps

parents 900d4029 ee86e7a9
Loading
Loading
Loading
Loading
Loading
+7 −1
Original line number Original line Diff line number Diff line
@@ -90,6 +90,7 @@ class MainActivity : AppCompatActivity() {
                    User.ANONYMOUS -> {
                    User.ANONYMOUS -> {
                        if (viewModel.authDataJson.value.isNullOrEmpty() && !viewModel.authRequestRunning) {
                        if (viewModel.authDataJson.value.isNullOrEmpty() && !viewModel.authRequestRunning) {
                            Log.d(TAG, "Fetching new authentication data")
                            Log.d(TAG, "Fetching new authentication data")
                            viewModel.setFirstTokenFetchTime()
                            viewModel.getAuthData()
                            viewModel.getAuthData()
                        }
                        }
                    }
                    }
@@ -99,6 +100,7 @@ class MainActivity : AppCompatActivity() {
                    User.GOOGLE -> {
                    User.GOOGLE -> {
                        if (viewModel.authData.value == null && !viewModel.authRequestRunning) {
                        if (viewModel.authData.value == null && !viewModel.authRequestRunning) {
                            Log.d(TAG, "Fetching new authentication data")
                            Log.d(TAG, "Fetching new authentication data")
                            viewModel.setFirstTokenFetchTime()
                            signInViewModel.fetchAuthData()
                            signInViewModel.fetchAuthData()
                        }
                        }
                    }
                    }
@@ -134,7 +136,11 @@ class MainActivity : AppCompatActivity() {
            if (it != true) {
            if (it != true) {
                Log.d(TAG, "Authentication data validation failed!")
                Log.d(TAG, "Authentication data validation failed!")
                viewModel.destroyCredentials { user ->
                viewModel.destroyCredentials { user ->
                    if (viewModel.isTimeEligibleForTokenRefresh()) {
                        generateAuthDataBasedOnUserType(user)
                        generateAuthDataBasedOnUserType(user)
                    } else {
                        Log.d(TAG, "Timeout validating auth data!")
                    }
                }
                }
            } else {
            } else {
                Log.d(TAG, "Authentication data is valid!")
                Log.d(TAG, "Authentication data is valid!")
+138 −1
Original line number Original line Diff line number Diff line
@@ -18,14 +18,18 @@


package foundation.e.apps
package foundation.e.apps


import android.app.AlertDialog
import android.app.Activity
import android.content.Context
import android.content.Context
import android.content.DialogInterface
import android.graphics.Bitmap
import android.graphics.Bitmap
import android.os.Build
import android.os.Build
import android.os.SystemClock
import android.util.Base64
import android.util.Base64
import android.util.Log
import android.util.Log
import android.view.KeyEvent
import android.widget.ImageView
import android.widget.ImageView
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.LiveData
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.MutableLiveData
@@ -38,16 +42,19 @@ import com.aurora.gplayapi.exceptions.ApiException
import com.google.gson.Gson
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import foundation.e.apps.api.cleanapk.blockedApps.BlockedAppRepository
import foundation.e.apps.api.cleanapk.blockedApps.BlockedAppRepository
import foundation.e.apps.api.fused.FusedAPIImpl
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.manager.workmanager.InstallWorkManager
import foundation.e.apps.manager.workmanager.InstallWorkManager
import foundation.e.apps.settings.SettingsFragment
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.enums.User
import foundation.e.apps.utils.enums.User
import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis
import foundation.e.apps.utils.modules.DataStoreModule
import foundation.e.apps.utils.modules.DataStoreModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
@@ -78,6 +85,116 @@ class MainActivityViewModel @Inject constructor(
    val purchaseDeclined: MutableLiveData<String> = MutableLiveData()
    val purchaseDeclined: MutableLiveData<String> = MutableLiveData()
    var authRequestRunning = false
    var authRequestRunning = false


    /*
     * Store the time when auth data is fetched for the first time.
     * If we try to fetch auth data after timeout, then don't allow it.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    var firstAuthDataFetchTime = 0L

    /*
     * Alert dialog to show to user if App Lounge times out.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    private lateinit var timeoutAlertDialog: AlertDialog

    /**
     * Display timeout alert dialog.
     *
     * @param activity Activity class. Basically the MainActivity.
     * @param positiveButtonBlock Code block when "Retry" is pressed.
     * @param openSettings Code block when "Open Settings" button is pressed.
     * This should open the [SettingsFragment] fragment.
     * @param applicationTypeFromPreferences Application type string, can be one of
     * [FusedAPIImpl.APP_TYPE_ANY], [FusedAPIImpl.APP_TYPE_OPEN], [FusedAPIImpl.APP_TYPE_PWA]
     */
    fun displayTimeoutAlertDialog(
        activity: Activity,
        positiveButtonBlock: () -> Unit,
        openSettings: () -> Unit,
        applicationTypeFromPreferences: String,
    ) {
        if (!this::timeoutAlertDialog.isInitialized) {
            timeoutAlertDialog = AlertDialog.Builder(activity).apply {
                setTitle(R.string.timeout_title)
                /*
                 * Prevent dismissing the dialog from pressing outside as it will only
                 * show a blank screen below the dialog.
                 */
                setCancelable(false)
                /*
                 * If user presses back button to close the dialog without selecting anything,
                 * close App Lounge.
                 */
                setOnKeyListener { dialog, keyCode, _ ->
                    if (keyCode == KeyEvent.KEYCODE_BACK) {
                        dialog.dismiss()
                        activity.finish()
                    }
                    true
                }
            }.create()
        }

        timeoutAlertDialog.apply {
            /*
             * Set retry button.
             */
            setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.retry)) { _, _ ->
                positiveButtonBlock()
            }
            /*
             * Set message based on apps from GPlay of cleanapk.
             */
            setMessage(
                activity.getString(
                    when (applicationTypeFromPreferences) {
                        FusedAPIImpl.APP_TYPE_ANY -> R.string.timeout_desc_gplay
                        else -> R.string.timeout_desc_cleanapk
                    }
                )
            )
            /*
             * Show "Open Setting" only for GPlay apps.
             */
            if (applicationTypeFromPreferences == FusedAPIImpl.APP_TYPE_ANY) {
                setButton(
                    DialogInterface.BUTTON_NEUTRAL,
                    activity.getString(R.string.open_settings)
                ) { _, _ ->
                    openSettings()
                }
            }
        }

        timeoutAlertDialog.show()
    }

    /**
     * Returns true if [timeoutAlertDialog] is displaying.
     * Returs false if it is not initialised.
     */
    fun isTimeoutDialogDisplayed(): Boolean {
        return if (this::timeoutAlertDialog.isInitialized) {
            timeoutAlertDialog.isShowing
        } else false
    }

    /**
     * Dismisses the [timeoutAlertDialog] if it is being displayed.
     * Does nothing if it is not being displayed.
     * Caller need not check if the dialog is being displayed.
     */
    fun dismissTimeoutDialog() {
        if (isTimeoutDialogDisplayed()) {
            try {
                timeoutAlertDialog.dismiss()
            } catch (_: Exception) {}
        }
    }

    // Downloads
    // Downloads
    val downloadList = fusedManagerRepository.getDownloadLiveList()
    val downloadList = fusedManagerRepository.getDownloadLiveList()
    var installInProgress = false
    var installInProgress = false
@@ -94,6 +211,26 @@ class MainActivityViewModel @Inject constructor(
        private const val TAG = "MainActivityViewModel"
        private const val TAG = "MainActivityViewModel"
    }
    }


    fun setFirstTokenFetchTime() {
        firstAuthDataFetchTime = SystemClock.uptimeMillis()
    }

    fun isTimeEligibleForTokenRefresh(): Boolean {
        return (SystemClock.uptimeMillis() - firstAuthDataFetchTime) <= timeoutDurationInMillis
    }

    /*
     * This method resets the last recorded token fetch time.
     * Then it posts authValidity as false. This causes the observer in MainActivity to destroyCredentials
     * and fetch new token.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    fun retryFetchingTokenAfterTimeout() {
        setFirstTokenFetchTime()
        authValidity.postValue(false)
    }

    fun getAuthData() {
    fun getAuthData() {
        if (!authRequestRunning) {
        if (!authRequestRunning) {
            authRequestRunning = true
            authRequestRunning = true
+84 −22
Original line number Original line Diff line number Diff line
@@ -20,6 +20,7 @@ package foundation.e.apps.api.fused


import android.content.Context
import android.content.Context
import android.text.format.Formatter
import android.text.format.Formatter
import android.util.Log
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.App
@@ -46,8 +47,11 @@ import foundation.e.apps.utils.enums.AppTag
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis
import foundation.e.apps.utils.modules.PWAManagerModule
import foundation.e.apps.utils.modules.PWAManagerModule
import foundation.e.apps.utils.modules.PreferenceManagerModule
import foundation.e.apps.utils.modules.PreferenceManagerModule
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Singleton
import javax.inject.Singleton


@@ -63,21 +67,72 @@ class FusedAPIImpl @Inject constructor(


    companion object {
    companion object {
        private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&"
        private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&"
        private const val APP_TYPE_ANY = "any"
        /*
        private const val APP_TYPE_OPEN = "open"
         * Removing "private" access specifier to allow access in
        private const val APP_TYPE_PWA = "pwa"
         * MainActivityViewModel.timeoutAlertDialog
         *
         * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
         */
        const val APP_TYPE_ANY = "any"
        const val APP_TYPE_OPEN = "open"
        const val APP_TYPE_PWA = "pwa"
        private const val CATEGORY_OPEN_GAMES_ID = "game_open_games"
        private const val CATEGORY_OPEN_GAMES_ID = "game_open_games"
        private const val CATEGORY_OPEN_GAMES_TITLE = "Open games"
        private const val CATEGORY_OPEN_GAMES_TITLE = "Open games"
    }
    }


    private var TAG = FusedAPIImpl::class.java.simpleName
    private var TAG = FusedAPIImpl::class.java.simpleName


    suspend fun getHomeScreenData(authData: AuthData): List<FusedHome> {
    /**
        val list = mutableListOf<FusedHome>()
     * Pass application source type along with list of apps.
     * Application source type may change in case of timeout of GPlay/cleanapk api.
     *
     * The second item of the Pair can be one of [APP_TYPE_ANY], [APP_TYPE_OPEN], [APP_TYPE_PWA].
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    suspend fun getHomeScreenData(authData: AuthData): Pair<List<FusedHome>, String> {
        val preferredApplicationType = preferenceManagerModule.preferredApplicationType()
        val preferredApplicationType = preferenceManagerModule.preferredApplicationType()
        val initialData = getHomeScreenDataBasedOnApplicationType(authData, preferredApplicationType)
        if (isFusedHomesEmpty(initialData.first)) {
            Log.d(TAG, "Received empty home data.")
        }
        return initialData
    }


        if (preferredApplicationType != APP_TYPE_ANY) {
    /**
            val response = if (preferredApplicationType == APP_TYPE_OPEN) {
     * Check if list in all the FusedHome is empty.
     * If any list is not empty, send false.
     * Else (if all lists are empty) send true.
     */
    fun isFusedHomesEmpty(fusedHomes: List<FusedHome>): Boolean {
        fusedHomes.forEach {
            if (it.list.isNotEmpty()) return false
        }
        return true
    }

    /*
     * Offload fetching application to a different method to dynamically fallback to a different
     * app source if the user selected app source times out.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    private suspend fun getHomeScreenDataBasedOnApplicationType(
        authData: AuthData,
        applicationType: String
    ): Pair<List<FusedHome>, String> {
        val list = mutableListOf<FusedHome>()
        try {
            /*
             * Each category of home apps (example "Top Free Apps") will have its own timeout.
             * Fetching 6 such categories will have a total timeout to 2 mins 30 seconds
             * (considering each category having 25 seconds timeout).
             *
             * To prevent waiting so long and fail early, use withTimeout{}.
             */
            withTimeout(timeoutDurationInMillis) {
                if (applicationType != APP_TYPE_ANY) {
                    val response = if (applicationType == APP_TYPE_OPEN) {
                        cleanAPKRepository.getHomeScreenData(
                        cleanAPKRepository.getHomeScreenData(
                            CleanAPKInterface.APP_TYPE_ANY,
                            CleanAPKInterface.APP_TYPE_ANY,
                            CleanAPKInterface.APP_SOURCE_FOSS
                            CleanAPKInterface.APP_SOURCE_FOSS
@@ -89,12 +144,19 @@ class FusedAPIImpl @Inject constructor(
                        ).body()
                        ).body()
                    }
                    }
                    response?.home?.let {
                    response?.home?.let {
                list.addAll(generateCleanAPKHome(it, preferredApplicationType))
                        list.addAll(generateCleanAPKHome(it, applicationType))
                    }
                    }
                } else {
                } else {
                    list.addAll(fetchGPlayHome(authData))
                    list.addAll(fetchGPlayHome(authData))
                }
                }
        return list
            }
        } catch (e: TimeoutCancellationException) {
            e.printStackTrace()
            Log.d(TAG, "Timed out fetching home data for type: $applicationType")
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return Pair(list, applicationType)
    }
    }


    suspend fun getCategoriesList(type: Category.Type, authData: AuthData): List<FusedCategory> {
    suspend fun getCategoriesList(type: Category.Type, authData: AuthData): List<FusedCategory> {
+5 −1
Original line number Original line Diff line number Diff line
@@ -34,10 +34,14 @@ import javax.inject.Singleton
class FusedAPIRepository @Inject constructor(
class FusedAPIRepository @Inject constructor(
    private val fusedAPIImpl: FusedAPIImpl
    private val fusedAPIImpl: FusedAPIImpl
) {
) {
    suspend fun getHomeScreenData(authData: AuthData): List<FusedHome> {
    suspend fun getHomeScreenData(authData: AuthData): Pair<List<FusedHome>, String> {
        return fusedAPIImpl.getHomeScreenData(authData)
        return fusedAPIImpl.getHomeScreenData(authData)
    }
    }


    fun isFusedHomesEmpty(fusedHomes: List<FusedHome>): Boolean {
        return fusedAPIImpl.isFusedHomesEmpty(fusedHomes)
    }

    suspend fun validateAuthData(authData: AuthData): Boolean {
    suspend fun validateAuthData(authData: AuthData): Boolean {
        return fusedAPIImpl.validateAuthData(authData)
        return fusedAPIImpl.validateAuthData(authData)
    }
    }
+6 −4
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import android.util.Log
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.network.IHttpClient
import com.aurora.gplayapi.network.IHttpClient
import foundation.e.apps.BuildConfig
import foundation.e.apps.BuildConfig
import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis
import okhttp3.Cache
import okhttp3.Cache
import okhttp3.Headers.Companion.toHeaders
import okhttp3.Headers.Companion.toHeaders
import okhttp3.HttpUrl
import okhttp3.HttpUrl
@@ -51,10 +52,11 @@ class GPlayHttpClient @Inject constructor(
    }
    }


    private val okHttpClient = OkHttpClient().newBuilder()
    private val okHttpClient = OkHttpClient().newBuilder()
        .connectTimeout(25, TimeUnit.SECONDS)
        .connectTimeout(timeoutDurationInMillis, TimeUnit.MILLISECONDS)
        .readTimeout(25, TimeUnit.SECONDS)
        .readTimeout(timeoutDurationInMillis, TimeUnit.MILLISECONDS)
        .writeTimeout(25, TimeUnit.SECONDS)
        .writeTimeout(timeoutDurationInMillis, TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(true)
        .callTimeout(timeoutDurationInMillis, TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(false)
        .followRedirects(true)
        .followRedirects(true)
        .followSslRedirects(true)
        .followSslRedirects(true)
        .cache(cache)
        .cache(cache)
Loading