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

Commit 7559690d authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Issue 1734: Remove duplicate data store

parent 40fe7d86
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import timber.log.Timber
import timber.log.Timber.Forest.plant
import java.util.concurrent.Executors
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking

@HiltAndroidApp
@DelicateCoroutinesApi
@@ -74,7 +76,7 @@ class AppLoungeApplication : Application(), Configuration.Provider {
        val pkgManagerBR = object : PkgManagerBR() {}
        registerReceiver(pkgManagerBR, pkgManagerModule.getFilter(), RECEIVER_EXPORTED)

        val currentVersion = dataStoreModule.getTOSVersion()
        val currentVersion = runBlocking { dataStoreModule.tosVersion.first() }
        if (!currentVersion.contentEquals(TOS_VERSION)) {
            MainScope().launch {
                dataStoreModule.saveTOCStatus(false, "")
+6 −3
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package foundation.e.apps.data.login

import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.preference.DataStoreModule
import foundation.e.apps.data.preference.PreferenceManagerModule
import javax.inject.Inject
import javax.inject.Singleton

@@ -28,11 +30,12 @@ import javax.inject.Singleton
 */
@Singleton
class CleanApkAuthenticator @Inject constructor(
    val loginData: LoginData,
    private val dataStoreModule: DataStoreModule,
    private val preferenceManagerModule: PreferenceManagerModule,
) : StoreAuthenticator {

    private val user: User
        get() = loginData.getUserType()
        get() = dataStoreModule.getUserType()

    override fun isStoreActive(): Boolean {
        if (user == User.UNAVAILABLE) {
@@ -41,7 +44,7 @@ class CleanApkAuthenticator @Inject constructor(
             */
            return false
        }
        return loginData.isOpenSourceSelected() || loginData.isPWASelected()
        return preferenceManagerModule.isOpenSourceSelected() || preferenceManagerModule.isPWASelected()
    }

    override suspend fun login(): AuthObject.CleanApk {
+16 −13
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package foundation.e.apps.data.login

import foundation.e.apps.data.Constants
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.preference.DataStoreModule
import foundation.e.apps.data.preference.PreferenceManagerModule
import javax.inject.Inject
import javax.inject.Singleton

@@ -30,33 +32,34 @@ import javax.inject.Singleton
 */
@Singleton
class LoginCommon @Inject constructor(
    private val loginData: LoginData,
    private val dataStoreModule: DataStoreModule,
    private val preferenceManagerModule: PreferenceManagerModule,
) {
    suspend fun saveUserType(user: User) {
        loginData.saveUserType(user)
        dataStoreModule.saveUserType(user)
    }

    fun getUserType(): User {
        return loginData.getUserType()
        return dataStoreModule.getUserType()
    }

    suspend fun saveGoogleLogin(email: String, oauth: String) {
        loginData.saveGoogleLogin(email, oauth)
        dataStoreModule.saveGoogleLogin(email, oauth)
    }

    suspend fun setNoGoogleMode() {
        loginData.setSource(Constants.PREFERENCE_SHOW_FOSS, true)
        loginData.setSource(Constants.PREFERENCE_SHOW_PWA, true)
        loginData.setSource(Constants.PREFERENCE_SHOW_GPLAY, false)
        loginData.saveUserType(User.NO_GOOGLE)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_FOSS, true)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_PWA, true)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_GPLAY, false)
        dataStoreModule.saveUserType(User.NO_GOOGLE)
    }

    suspend fun logout() {
        loginData.destroyCredentials()
        loginData.clearUserType()
        dataStoreModule.destroyCredentials()
        dataStoreModule.saveUserType(null)
        // reset app source preferences on logout.
        loginData.setSource(Constants.PREFERENCE_SHOW_FOSS, true)
        loginData.setSource(Constants.PREFERENCE_SHOW_PWA, true)
        loginData.setSource(Constants.PREFERENCE_SHOW_GPLAY, true)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_FOSS, true)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_PWA, true)
        preferenceManagerModule.setSource(Constants.PREFERENCE_SHOW_GPLAY, true)
    }
}
+0 −188
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2022  E FOUNDATION
 *
 * 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.data.login

import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.preference.PreferenceManager
import com.aurora.gplayapi.data.models.AuthData
import com.google.gson.Gson
import dagger.hilt.android.qualifiers.ApplicationContext
import foundation.e.apps.data.Constants.PREFERENCE_SHOW_FOSS
import foundation.e.apps.data.Constants.PREFERENCE_SHOW_GPLAY
import foundation.e.apps.data.Constants.PREFERENCE_SHOW_PWA
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.preference.DataStoreModule.Companion.dataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class LoginData @Inject constructor(
    @ApplicationContext
    private val context: Context,
    private val gson: Gson
) {

    private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(context)

    private val AUTHDATA = stringPreferencesKey("authData")
    private val EMAIL = stringPreferencesKey("email")
    private val OAUTHTOKEN = stringPreferencesKey("oauthtoken")
    private val AASTOKEN = stringPreferencesKey("aasToken")
    private val USERTYPE = stringPreferencesKey("userType")

    /*
     * Difference between OAUTHTOKEN and AASTOKEN:
     *
     * These two are used only for Google login, not for Anonymous login.
     * OAuthToken is obtained from the Google Login web page, from the cookies.
     * This OAuthToken is then used by AC2DMTask in GPlayAPIImpl class
     * to generate AasToken.
     *
     * To get Google Play Store data, we need to create an AuthData instance.
     * For Google user, this can only be done using AasToken, not OAuthToken.
     *
     * Very important: AasToken can be generated only ONCE from one OAuthToken.
     * We cannot get AasToken again from the same OAuthToken. Thus it is
     * important to safely store the AasToken to regenerate AuthData if needed.
     * If AasToken is not stored, user has to logout and login again.
     */

    private val authData = context.dataStore.data.map { it[AUTHDATA] ?: "" }
    private val emailData = context.dataStore.data.map { it[EMAIL] ?: "" }
    private val aasToken = context.dataStore.data.map { it[AASTOKEN] ?: "" }
    private val oauthToken = context.dataStore.data.map { it[OAUTHTOKEN] ?: "" }
    private val userType = context.dataStore.data.map { it[USERTYPE] ?: "" }

    // Setters

    suspend fun saveAuthData(authData: AuthData) {
        context.dataStore.edit {
            it[AUTHDATA] = gson.toJson(authData)
        }
    }

    suspend fun saveUserType(user: User) {
        context.dataStore.edit {
            it[USERTYPE] = user.name
        }
    }

    suspend fun saveGoogleLogin(email: String, token: String) {
        context.dataStore.edit {
            it[EMAIL] = email
            it[OAUTHTOKEN] = token
        }
    }

    suspend fun saveAasToken(aasToken: String) {
        context.dataStore.edit {
            it[AASTOKEN] = aasToken
        }
    }

    // Getters

    fun getAuthData(): String {
        return runBlocking {
            authData.first()
        }
    }

    /**
     * Get the [User] type stored in the data store.
     * In case nothing is stored, returns [User.UNAVAILABLE].
     *
     * No need to wrap this function in try-catch block.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5680
     */
    fun getUserType(): User {
        return runBlocking {
            userType.first().run {
                val userStrings = User.values().map { it.name }
                if (this !in userStrings) User.UNAVAILABLE
                else User.valueOf(this)
            }
        }
    }

    fun getEmail(): String {
        return runBlocking {
            emailData.first()
        }
    }

    fun getOAuthToken(): String {
        return runBlocking {
            oauthToken.first()
        }
    }

    fun getAASToken(): String {
        return runBlocking {
            aasToken.first()
        }
    }

    fun isOpenSourceSelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_FOSS, true)
    fun isPWASelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_PWA, true)
    fun isGplaySelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_GPLAY, true)

    fun setSource(source: String, value: Boolean) {
        val editor = preferenceManager.edit()
        editor.run {
            this.putBoolean(source, value)
        }
        editor.apply()
    }

    // Clear data

    /**
     * Destroy auth credentials if they are no longer valid.
     *
     * Modification for issue: https://gitlab.e.foundation/e/backlog/-/issues/5168
     * Previously this method would also remove [USERTYPE].
     * To clear this value, call [clearUserType].
     */
    suspend fun destroyCredentials() {
        context.dataStore.edit {
            it.remove(AUTHDATA)
            it.remove(EMAIL)
            it.remove(OAUTHTOKEN)
            it.remove(AASTOKEN)
        }
    }

    suspend fun clearAuthData() {
        context.dataStore.edit {
            it.remove(AUTHDATA)
        }
    }

    suspend fun clearUserType() {
        context.dataStore.edit {
            it.remove(USERTYPE)
        }
    }
}
+15 −11
Original line number Diff line number Diff line
@@ -28,7 +28,10 @@ import foundation.e.apps.data.login.api.PlayStoreLoginManagerFactory
import foundation.e.apps.data.login.api.PlayStoreLoginManager
import foundation.e.apps.data.login.api.GoogleLoginManager
import foundation.e.apps.data.login.api.PlayStoreLoginWrapper
import foundation.e.apps.data.preference.DataStoreModule
import foundation.e.apps.data.preference.PreferenceManagerModule
import foundation.e.apps.data.retryWithBackoff
import foundation.e.apps.data.preference.getSync
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
@@ -44,14 +47,15 @@ import javax.inject.Singleton
class PlayStoreAuthenticator @Inject constructor(
    @ApplicationContext private val context: Context,
    private val gson: Gson,
    private val loginData: LoginData,
    private val dataStoreModule: DataStoreModule,
    private val preferenceManagerModule: PreferenceManagerModule,
) : StoreAuthenticator, AuthDataValidator {

    @Inject
    lateinit var loginManagerFactory: PlayStoreLoginManagerFactory

    private val user: User
        get() = loginData.getUserType()
        get() = dataStoreModule.getUserType()

    private val loginManager: PlayStoreLoginManager
        get() = loginManagerFactory.createLoginManager(user)
@@ -69,7 +73,7 @@ class PlayStoreAuthenticator @Inject constructor(
             */
            return false
        }
        return loginData.isGplaySelected()
        return preferenceManagerModule.isGplaySelected()
    }

    /**
@@ -106,7 +110,7 @@ class PlayStoreAuthenticator @Inject constructor(
    }

    override suspend fun logout() {
        loginData.clearAuthData()
        dataStoreModule.saveAuthData(null)
    }

    /**
@@ -114,7 +118,7 @@ class PlayStoreAuthenticator @Inject constructor(
     * Returns null if nothing is saved.
     */
    private fun getSavedAuthData(): AuthData? {
        val authJson = loginData.getAuthData()
        val authJson = dataStoreModule.authData.getSync()
        return if (authJson.isBlank()) null
        else try {
            gson.fromJson(authJson, AuthData::class.java)
@@ -125,14 +129,14 @@ class PlayStoreAuthenticator @Inject constructor(
    }

    private suspend fun saveAuthData(authData: AuthData) {
        loginData.saveAuthData(authData)
        dataStoreModule.saveAuthData(authData)
    }

    /**
     * Generate new AuthData based on the user type.
     */
    private suspend fun generateAuthData(): ResultSupreme<AuthData?> {
        return when (loginData.getUserType()) {
        return when (dataStoreModule.getUserType()) {
            User.ANONYMOUS -> getAuthDataAnonymously()
            User.GOOGLE -> getAuthDataWithGoogleAccount()
            else -> ResultSupreme.Error("User type not ANONYMOUS or GOOGLE")
@@ -157,9 +161,9 @@ class PlayStoreAuthenticator @Inject constructor(

    private suspend fun getAuthDataWithGoogleAccount(): ResultSupreme<AuthData?> {

        val email = loginData.getEmail()
        val oauthToken = loginData.getOAuthToken()
        val aasToken = loginData.getAASToken()
        val email = dataStoreModule.emailData.getSync()
        val oauthToken = dataStoreModule.oauthToken.getSync()
        val aasToken = dataStoreModule.aasToken.getSync()
        /*
         * If aasToken is not blank, means it was stored successfully from a previous Google login.
         * Use it to fetch auth data.
@@ -195,7 +199,7 @@ class PlayStoreAuthenticator @Inject constructor(
        /*
         * Finally save the aasToken and create auth data.
         */
        loginData.saveAasToken(aasTokenFetched)
        dataStoreModule.saveAasToken(aasTokenFetched)
        return loginWrapper.login(locale).run {
            if (isSuccess()) ResultSupreme.Success(formatAuthData(this.data!!))
            else this
Loading