Loading app/src/main/java/foundation/e/apps/MainActivity.kt +18 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -111,6 +112,10 @@ class MainActivity : AppCompatActivity() { } } viewModel.errorAuthResponse.observe(this) { onSignInError() } viewModel.authValidity.observe(this) { viewModel.handleAuthValidity(it) { Timber.d("Timeout validating auth data!") Loading Loading @@ -281,6 +286,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) Loading app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +72 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -51,6 +53,7 @@ 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 Loading Loading @@ -86,6 +89,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. Loading Loading @@ -133,7 +142,19 @@ class MainActivityViewModel @Inject constructor( fun retryFetchingTokenAfterTimeout() { firstAuthDataFetchTime = 0 setFirstTokenFetchTime() authValidity.postValue(false) if (isUserTypeGoogle()) { /* * 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 { validateAuthData() } } else { postFalseAuthValidity() } } fun uploadFaultyTokenToEcloud(description: String) { Loading Loading @@ -163,7 +184,7 @@ class MainActivityViewModel @Inject constructor( */ if (!fusedAPIRepository.fetchAuthData()) { authRequestRunning = false authValidity.postValue(false) postFalseAuthValidity() } } } Loading Loading @@ -211,13 +232,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 Loading Loading @@ -291,7 +332,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() Loading @@ -313,7 +379,7 @@ class MainActivityViewModel @Inject constructor( } } private suspend fun isAuthValid(authData: AuthData): Boolean { private suspend fun getAuthValidityResponse(authData: AuthData): PlayResponse { return fusedAPIRepository.validateAuthData(authData) } Loading app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R Loading Loading @@ -374,7 +375,7 @@ class FusedAPIImpl @Inject constructor( return gPlayAPIRepository.fetchAuthData(email, aasToken) } suspend fun validateAuthData(authData: AuthData): Boolean { suspend fun validateAuthData(authData: AuthData): PlayResponse { return gPlayAPIRepository.validateAuthData(authData) } Loading app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt +6 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.fused.data.FusedCategory Loading Loading @@ -53,10 +54,11 @@ class FusedAPIRepository @Inject constructor( return fusedAPIImpl.getApplicationCategoryPreference() } suspend fun validateAuthData(authData: AuthData): Boolean { return authData.authToken.isNotEmpty() && authData.deviceInfoProvider != null && fusedAPIImpl.validateAuthData( authData ) suspend fun validateAuthData(authData: AuthData): PlayResponse { if (authData.authToken.isNotEmpty() && authData.deviceInfoProvider != null) { return fusedAPIImpl.validateAuthData(authData) } return PlayResponse() } suspend fun getApplicationDetails( Loading app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt +8 −7 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.AuthValidator import com.aurora.gplayapi.helpers.CategoryHelper Loading @@ -39,6 +40,7 @@ import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.api.gplay.token.TokenRepository import foundation.e.apps.api.gplay.utils.CustomAuthValidator import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.utils.modules.DataStoreModule import kotlinx.coroutines.Dispatchers Loading Loading @@ -83,19 +85,18 @@ class GPlayAPIImpl @Inject constructor( return null } suspend fun validateAuthData(authData: AuthData): Boolean { var validity: Boolean suspend fun validateAuthData(authData: AuthData): PlayResponse { var result = PlayResponse() withContext(Dispatchers.IO) { validity = try { val authValidator = AuthValidator(authData).using(gPlayHttpClient) authValidator.isValid() try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { e.printStackTrace() throw e false } } return validity return result } suspend fun getSearchSuggestions(query: String, authData: AuthData): List<SearchSuggestEntry> { Loading Loading
app/src/main/java/foundation/e/apps/MainActivity.kt +18 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -111,6 +112,10 @@ class MainActivity : AppCompatActivity() { } } viewModel.errorAuthResponse.observe(this) { onSignInError() } viewModel.authValidity.observe(this) { viewModel.handleAuthValidity(it) { Timber.d("Timeout validating auth data!") Loading Loading @@ -281,6 +286,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) Loading
app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +72 −6 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -51,6 +53,7 @@ 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 Loading Loading @@ -86,6 +89,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. Loading Loading @@ -133,7 +142,19 @@ class MainActivityViewModel @Inject constructor( fun retryFetchingTokenAfterTimeout() { firstAuthDataFetchTime = 0 setFirstTokenFetchTime() authValidity.postValue(false) if (isUserTypeGoogle()) { /* * 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 { validateAuthData() } } else { postFalseAuthValidity() } } fun uploadFaultyTokenToEcloud(description: String) { Loading Loading @@ -163,7 +184,7 @@ class MainActivityViewModel @Inject constructor( */ if (!fusedAPIRepository.fetchAuthData()) { authRequestRunning = false authValidity.postValue(false) postFalseAuthValidity() } } } Loading Loading @@ -211,13 +232,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 Loading Loading @@ -291,7 +332,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() Loading @@ -313,7 +379,7 @@ class MainActivityViewModel @Inject constructor( } } private suspend fun isAuthValid(authData: AuthData): Boolean { private suspend fun getAuthValidityResponse(authData: AuthData): PlayResponse { return fusedAPIRepository.validateAuthData(authData) } Loading
app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +2 −1 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.R Loading Loading @@ -374,7 +375,7 @@ class FusedAPIImpl @Inject constructor( return gPlayAPIRepository.fetchAuthData(email, aasToken) } suspend fun validateAuthData(authData: AuthData): Boolean { suspend fun validateAuthData(authData: AuthData): PlayResponse { return gPlayAPIRepository.validateAuthData(authData) } Loading
app/src/main/java/foundation/e/apps/api/fused/FusedAPIRepository.kt +6 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import foundation.e.apps.api.ResultSupreme import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.api.fused.data.FusedCategory Loading Loading @@ -53,10 +54,11 @@ class FusedAPIRepository @Inject constructor( return fusedAPIImpl.getApplicationCategoryPreference() } suspend fun validateAuthData(authData: AuthData): Boolean { return authData.authToken.isNotEmpty() && authData.deviceInfoProvider != null && fusedAPIImpl.validateAuthData( authData ) suspend fun validateAuthData(authData: AuthData): PlayResponse { if (authData.authToken.isNotEmpty() && authData.deviceInfoProvider != null) { return fusedAPIImpl.validateAuthData(authData) } return PlayResponse() } suspend fun getApplicationDetails( Loading
app/src/main/java/foundation/e/apps/api/gplay/GPlayAPIImpl.kt +8 −7 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import com.aurora.gplayapi.data.models.File import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.PlayResponse import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.AuthValidator import com.aurora.gplayapi.helpers.CategoryHelper Loading @@ -39,6 +40,7 @@ import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.TopChartsHelper import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.api.gplay.token.TokenRepository import foundation.e.apps.api.gplay.utils.CustomAuthValidator import foundation.e.apps.api.gplay.utils.GPlayHttpClient import foundation.e.apps.utils.modules.DataStoreModule import kotlinx.coroutines.Dispatchers Loading Loading @@ -83,19 +85,18 @@ class GPlayAPIImpl @Inject constructor( return null } suspend fun validateAuthData(authData: AuthData): Boolean { var validity: Boolean suspend fun validateAuthData(authData: AuthData): PlayResponse { var result = PlayResponse() withContext(Dispatchers.IO) { validity = try { val authValidator = AuthValidator(authData).using(gPlayHttpClient) authValidator.isValid() try { val authValidator = CustomAuthValidator(authData).using(gPlayHttpClient) result = authValidator.getValidityResponse() } catch (e: Exception) { e.printStackTrace() throw e false } } return validity return result } suspend fun getSearchSuggestions(query: String, authData: AuthData): List<SearchSuggestEntry> { Loading