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

Commit 79404e38 authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Merge branch 'main' into 5413-timeout_improvement

# Conflicts:
#	app/src/main/java/foundation/e/apps/MainActivityViewModel.kt
#	app/src/main/java/foundation/e/apps/updates/manager/UpdatesManagerImpl.kt
parents 54c922d9 0347130f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ plugins {

def versionMajor = 2
def versionMinor = 3
def versionPatch = 0
def versionPatch = 2

android {
    compileSdk 31
+25 −2
Original line number Diff line number Diff line
/*
 * Copyright ECORP SAS 2022
 * 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 <https://www.gnu.org/licenses/>.
 */

package foundation.e.apps

import androidx.lifecycle.ViewModel
@@ -32,14 +50,19 @@ class AppProgressViewModel @Inject constructor(
            if (!isProgressValidForApp(fusedApp, progress)) {
                return -1
            }

            val downloadingMap = progress.totalSizeBytes.filter { item ->
                appDownload.downloadIdMap.keys.contains(item.key)
                appDownload.downloadIdMap.keys.contains(item.key) && item.value > 0
            }

            if (appDownload.downloadIdMap.size > downloadingMap.size) { // All files for download are not ready yet
                return 0
            }

            val totalSizeBytes = downloadingMap.values.sum()
            val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item ->
                appDownload.downloadIdMap.keys.contains(item.key)
            }.values.sum()

            return ((downloadedSoFar / totalSizeBytes.toDouble()) * 100).toInt()
        }
        return 0
+36 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Environment
import android.os.StatFs
import android.os.storage.StorageManager
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@@ -43,8 +44,12 @@ import foundation.e.apps.purchase.AppPurchaseFragmentDirections
import foundation.e.apps.setup.signin.SignInViewModel
import foundation.e.apps.updates.UpdatesNotifier
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.eventBus.AppEvent
import foundation.e.apps.utils.eventBus.EventBus
import foundation.e.apps.utils.modules.CommonUtilsModule
import foundation.e.apps.utils.parentFragment.TimeoutFragment
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
@@ -111,6 +116,10 @@ class MainActivity : AppCompatActivity() {
            }
        }

        viewModel.errorAuthResponse.observe(this) {
            onSignInError()
        }

        viewModel.authValidity.observe(this) {
            viewModel.handleAuthValidity(it) {
                Timber.d("Timeout validating auth data!")
@@ -199,6 +208,19 @@ class MainActivity : AppCompatActivity() {
        }

        viewModel.updateAppWarningList()

        lifecycleScope.launchWhenResumed {
            EventBus.events.filter { appEvent ->
                appEvent is AppEvent.SignatureMissMatchError
            }.collectLatest {
                val appName = viewModel.getAppNameByPackageName(it.data.toString())
                ApplicationDialogFragment(
                    title = getString(R.string.update_error),
                    message = getString(R.string.error_signature_mismatch, appName),
                    positiveButtonText = getString(R.string.ok)
                ).show(supportFragmentManager, TAG)
            }
        }
    }

    private fun handleFusedDownloadQueued(
@@ -246,7 +268,7 @@ class MainActivity : AppCompatActivity() {
    }

    fun showSnackbarMessage(message: String) {
        Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show()
        Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
    }

    private fun showNoInternet() {
@@ -281,6 +303,19 @@ class MainActivity : AppCompatActivity() {
        }
    }

    private fun onSignInError() {
        AlertDialog.Builder(this).apply {
            setTitle(R.string.sign_in_failed_title)
            setMessage(R.string.sign_in_failed_desc)
            setPositiveButton(R.string.retry) { _, _ ->
                viewModel.retryFetchingTokenAfterTimeout()
            }
            setNegativeButton(R.string.logout) { _, _ ->
                viewModel.postFalseAuthValidity()
            }
        }.show()
    }

    private fun getAvailableInternalMemorySize(): Long {
        val path: File = Environment.getDataDirectory()
        val stat = StatFs(path.path)
+104 −9
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import androidx.lifecycle.asLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse
import com.aurora.gplayapi.exceptions.ApiException
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -42,6 +43,7 @@ import foundation.e.apps.api.ecloud.EcloudRepository
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.gplay.utils.AC2DMTask
import foundation.e.apps.api.gplay.utils.AC2DMUtil
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.manager.pkg.PkgManagerModule
@@ -49,6 +51,9 @@ import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
import foundation.e.apps.utils.enums.User
import foundation.e.apps.utils.enums.isInitialized
import foundation.e.apps.utils.enums.isUnFiltered
import foundation.e.apps.utils.modules.CommonUtilsModule.NETWORK_CODE_SUCCESS
import foundation.e.apps.utils.modules.CommonUtilsModule.timeoutDurationInMillis
import foundation.e.apps.utils.modules.DataStoreModule
import kotlinx.coroutines.Dispatchers
@@ -86,6 +91,12 @@ class MainActivityViewModel @Inject constructor(
    val purchaseDeclined: MutableLiveData<String> = MutableLiveData()
    var authRequestRunning = false

    /*
     * If this live data is populated, it means Google sign in failed.
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5709
     */
    val errorAuthResponse = MutableLiveData<PlayResponse>()

    /*
     * 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.
@@ -142,11 +153,26 @@ class MainActivityViewModel @Inject constructor(
    fun checkTokenOnTimeout() {
        firstAuthDataFetchTime = 0
        setFirstTokenFetchTime()
        if (authData.value != null) {
            validateAuthData()
        /*
         * Change done to show sign in error dialog for Google login.
         * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5709
         */
        if (authDataJson.value.isNullOrEmpty()) {
            generateAuthDataBasedOnUserType(User.GOOGLE.name)
        } else {
            authValidity.postValue(false)
            validateAuthData()
        }

        /*
        * Old code from branch: 5413-timeout-improvement:
        * Commented out as this logic is already expected to be fulfilled
        * by the above code.
        */
        // if (authData.value != null) {
        //     validateAuthData()
        // } else {
        //     authValidity.postValue(false)
        // }
    }

    fun uploadFaultyTokenToEcloud(description: String) {
@@ -176,7 +202,7 @@ class MainActivityViewModel @Inject constructor(
                 */
                if (!fusedAPIRepository.fetchAuthData()) {
                    authRequestRunning = false
                    authValidity.postValue(false)
                    postFalseAuthValidity()
                }
            }
        }
@@ -224,13 +250,33 @@ class MainActivityViewModel @Inject constructor(
    fun validateAuthData() {
        viewModelScope.launch {
            jsonToAuthData()?.let {
                val isAuthValid = isAuthValid(it)
                authValidity.postValue(isAuthValid)
                val validityResponse = getAuthValidityResponse(it)
                if (isUserTypeGoogle() && validityResponse.code != NETWORK_CODE_SUCCESS) {
                    /*
                     * Change done to show sign in error dialog for Google login.
                     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5709
                     */
                    errorAuthResponse.postValue(validityResponse)
                } else {
                    authValidity.postValue(validityResponse.isSuccessful)
                }
                authRequestRunning = false
            }
        }
    }

    // Issue: https://gitlab.e.foundation/e/backlog/-/issues/5709
    fun isUserTypeGoogle(): Boolean {
        return userType.value == User.GOOGLE.name
    }

    /**
     * Useful to destroy credentials.
     */
    fun postFalseAuthValidity() {
        authValidity.postValue(false)
    }

    fun handleAuthDataJson() {
        val user = userType.value
        val json = authDataJson.value
@@ -304,7 +350,32 @@ class MainActivityViewModel @Inject constructor(
        var responseMap: Map<String, String>
        withContext(Dispatchers.IO) {
            val response = aC2DMTask.getAC2DMResponse(email, oauthToken)
            responseMap = response
            responseMap = if (response.isSuccessful) {
                AC2DMUtil.parseResponse(String(response.responseBytes))
            } else {
                mapOf()
            }
            if (isUserTypeGoogle() && response.code != NETWORK_CODE_SUCCESS) {
                /*
                 * For google login, the email and aasToken gets stored when
                 * we login through the webview, but that does not mean we have a valid authData.
                 *
                 * For first login, control flow is as below:
                 * In MainActivity, from userType observer -> handleAuthDataJson
                 * -> generateAuthDataBasedOnUserType -> doFetchAuthData -> this function
                 *
                 * If for first google login, google sign in portal was available
                 * but android.clients.google.com is unreachable, then responseMap is blank.
                 *
                 * We see validateAuthData is never called (which had a check for incorrect response)
                 * Hence we have to check the response code is NETWORK_CODE_SUCCESS (200) or not
                 * and show the Google sign in failed dialog.
                 *
                 * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5709
                 */
                errorAuthResponse.postValue(response)
                return@withContext
            }
            responseMap["Token"]?.let {
                if (fusedAPIRepository.fetchAuthData(email, it) == null) {
                    dataStoreModule.clearUserType()
@@ -326,7 +397,7 @@ class MainActivityViewModel @Inject constructor(
        }
    }

    private suspend fun isAuthValid(authData: AuthData): Boolean {
    private suspend fun getAuthValidityResponse(authData: AuthData): PlayResponse {
        return fusedAPIRepository.validateAuthData(authData)
    }

@@ -376,7 +447,7 @@ class MainActivityViewModel @Inject constructor(
        fusedApp: FusedApp,
        alertDialogContext: Context? = null
    ): Boolean {
        if (!fusedApp.isFree && fusedApp.price.isBlank()) {
        if (!fusedApp.filterLevel.isUnFiltered()) {
            alertDialogContext?.let { context ->
                AlertDialog.Builder(context).apply {
                    setTitle(R.string.unsupported_app_title)
@@ -394,6 +465,26 @@ class MainActivityViewModel @Inject constructor(
        return false
    }

    /**
     * Fetch the filter level of an app and perform some action.
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720
     */
    fun verifyUiFilter(fusedApp: FusedApp, method: () -> Unit) {
        viewModelScope.launch {
            val authData = authData.value
            if (fusedApp.filterLevel.isInitialized()) {
                method()
            } else {
                fusedAPIRepository.getAppFilterLevel(fusedApp, authData).run {
                    if (isInitialized()) {
                        fusedApp.filterLevel = this
                        method()
                    }
                }
            }
        }
    }

    fun getApplication(app: FusedApp, imageView: ImageView?) {
        if (shouldShowPaidAppsSnackBar(app)) {
            return
@@ -561,4 +652,8 @@ class MainActivityViewModel @Inject constructor(
    fun updateAppWarningList() {
        blockedAppRepository.fetchUpdateOfAppWarningList()
    }

    fun getAppNameByPackageName(packageName: String): String {
        return pkgManagerModule.getAppNameFromPackageName(packageName)
    }
}
+24 −20
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ class DownloadManager @Inject constructor(
        filePath: String = "",
        downloadCompleted: ((Boolean, String) -> Unit)?
    ) {
        try {
            downloadManager.query(downloadManagerQuery.setFilterById(downloadId))
                .use { cursor ->
                    if (cursor.moveToFirst()) {
@@ -90,6 +91,9 @@ class DownloadManager @Inject constructor(
                        }
                    }
                }
        } catch (e: Exception) {
            Timber.e(e)
        }
    }

    private fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
Loading