From fd94d042846069289627b2f03128672632f86027 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Wed, 28 Jan 2026 08:00:43 +0100 Subject: [PATCH 1/2] fix: remove useless faulty token report Do not report a faulty token to the token dispenser when signed in with the user Google account. --- app/src/main/java/foundation/e/apps/ui/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index a78392e8b..68b2d7ed2 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -398,7 +398,7 @@ class MainActivity : AppCompatActivity() { gPlayAuthObject?.result?.run { if (isSuccess()) { viewModel.gPlayAuthData = data as AuthData - } else if (exception is GPlayValidationException) { + } else if (exception is GPlayValidationException && viewModel.getUser() == User.ANONYMOUS) { val email = otherPayload.toString() viewModel.uploadFaultyTokenToEcloud( email, -- GitLab From b0fb099de80f47bd8553cdb1a15e8825fd37eeaf Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Wed, 28 Jan 2026 08:40:39 +0100 Subject: [PATCH 2/2] refactor: introduce domain ReportFaultyTokenUseCase - move observeAuthObjects() logic out of the MainActivity - get AuthData from AppLoungeDataStore directly. This removes a weird dependency from SettingsFragment to the MainActivityViewModel. --- app/detekt-baseline.xml | 12 +- .../domain/login/ReportFaultyTokenUseCase.kt | 39 +++++ .../java/foundation/e/apps/ui/MainActivity.kt | 45 ++---- .../e/apps/ui/MainActivityViewModel.kt | 142 ++++++++++++------ .../e/apps/ui/settings/SettingsFragment.kt | 20 ++- .../login/ReportFaultyTokenUseCaseTest.kt | 64 ++++++++ 6 files changed, 221 insertions(+), 101 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCase.kt create mode 100644 app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 1c47dd40d..8968baf74 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -12,12 +12,10 @@ LongParameterList:AppManagerImpl.kt$AppManagerImpl$( @Named("cacheDir") private val cacheDir: String, private val downloadManager: DownloadManager, private val notificationManager: NotificationManager, private val appInstallRepository: AppInstallRepository, private val pwaManager: PwaManager, private val appLoungePackageManager: AppLoungePackageManager, @Named("download") private val downloadNotificationChannel: NotificationChannel, @Named("update") private val updateNotificationChannel: NotificationChannel, @ApplicationContext private val context: Context ) LongParameterList:ApplicationDialogFragment.kt$ApplicationDialogFragment$( title: String, message: String, @DrawableRes drawableResId: Int = -1, drawable: Drawable? = null, positiveButtonText: String = "", positiveButtonAction: (() -> Unit)? = null, cancelButtonText: String = "", cancelButtonAction: (() -> Unit)? = null, cancelable: Boolean = true, onDismissListener: (() -> Unit)? = null, ) LongParameterList:ApplicationListRVAdapter.kt$ApplicationListRVAdapter$( private val applicationInstaller: ApplicationInstaller, private val privacyInfoViewModel: PrivacyInfoViewModel, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val currentDestinationId: Int, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((Application) -> Unit)? = null ) - LongParameterList:CleanApkRetrofit.kt$CleanApkRetrofit$( @Query("keyword") keyword: String, @Query("source") source: String = APP_SOURCE_FOSS, @Query("type") type: String = APP_TYPE_ANY, @Query("nres") nres: Int = 20, @Query("page") page: Int = 1, @Query("by") by: String? = null, ) LongParameterList:EglExtensionProvider.kt$EglExtensionProvider$( egl10: EGL10, eglDisplay: EGLDisplay, eglConfig: EGLConfig?, ai: IntArray, ai1: IntArray?, set: MutableSet<String> ) - LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PwaManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val gPlayContentRatingRepository: GPlayContentRatingRepository, private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, private val appInstallProcessor: AppInstallProcessor, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, ) + LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PwaManager, private val blockedAppRepository: BlockedAppRepository, private val gPlayContentRatingRepository: GPlayContentRatingRepository, private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, private val appInstallProcessor: AppInstallProcessor, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, private val reportFaultyTokenUseCase: ReportFaultyTokenUseCase, ) LongParameterList:UpdatesManagerImpl.kt$UpdatesManagerImpl$( @ApplicationContext private val context: Context, private val appLoungePackageManager: AppLoungePackageManager, private val applicationRepository: ApplicationRepository, private val faultyAppRepository: FaultyAppRepository, private val appLoungePreference: AppLoungePreference, private val fDroidRepository: FDroidRepository, private val blockedAppRepository: BlockedAppRepository, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, ) LongParameterList:UpdatesWorker.kt$UpdatesWorker$( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val updatesManagerRepository: UpdatesManagerRepository, private val appLoungeDataStore: AppLoungeDataStore, private val authenticatorRepository: AuthenticatorRepository, private val appInstallProcessor: AppInstallProcessor, private val blockedAppRepository: BlockedAppRepository, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, ) - NoWildcardImports:SystemAppsUpdatesRepository.kt$import foundation.e.apps.data.gitlab.UpdatableSystemAppsApi.* ProtectedMemberInFinalClass:ApplicationListFragment.kt$ApplicationListFragment$// protected to avoid SyntheticAccessor protected val args: ApplicationListFragmentArgs by navArgs() ProtectedMemberInFinalClass:ApplicationListFragment.kt$ApplicationListFragment$// protected to avoid SyntheticAccessor protected val viewModel: ApplicationListViewModel by viewModels() ProtectedMemberInFinalClass:GoogleSignInFragment.kt$GoogleSignInFragment$// protected to avoid SyntheticAccessor protected val viewModel: LoginViewModel by lazy { ViewModelProvider(requireActivity())[LoginViewModel::class.java] } @@ -32,8 +30,6 @@ ReturnCount:DownloadManager.kt$DownloadManager$fun getSizeRequired(downloadId: Long): Long ReturnCount:DownloadManager.kt$DownloadManager$private fun sanitizeStatus(downloadId: Long, status: Int, reason: Int): Int ReturnCount:Extensions.kt$fun Context.isNetworkAvailable(): Boolean - ReturnCount:PlayStoreAuthenticator.kt$PlayStoreAuthenticator$override suspend fun validateAuthData(): ResultSupreme<AuthData?> - ReturnCount:PlayStoreAuthenticator.kt$PlayStoreAuthenticator$private suspend fun getAuthDataWithGoogleAccount(): ResultSupreme<AuthData?> ReturnCount:PrivacyInfoViewModel.kt$PrivacyInfoViewModel$fun shouldRequestExodusReport(application: Application?): Boolean ReturnCount:StorageNotificationManager.kt$StorageNotificationManager$private fun getSpaceMissing(appInstall: AppInstall, downloadId: Long? = null): Long ReturnCount:SystemAppsUpdatesRepository.kt$SystemAppsUpdatesRepository$private suspend fun getApplication( packageName: String, releaseType: OsReleaseType, sdkLevel: Int, device: String, ): Application? @@ -52,11 +48,9 @@ SwallowedException:ApplicationViewModel.kt$ApplicationViewModel$e: Exception SwallowedException:ApplicationViewModel.kt$ApplicationViewModel$e: InternalException.AppNotFound SwallowedException:GPlayHttpClient.kt$GPlayHttpClient$e: Exception - SwallowedException:NativeDeviceInfoProviderModule.kt$NativeDeviceInfoProviderModule$e: Exception SwallowedException:NativeGsfVersionProvider.kt$NativeGsfVersionProvider$e: PackageManager.NameNotFoundException SwallowedException:UpdatesManagerImpl.kt$UpdatesManagerImpl$e: Exception TooGenericExceptionCaught:AgeRatingProvider.kt$AgeRatingProvider$e: Exception - TooGenericExceptionCaught:AnonymousLoginManager.kt$AnonymousLoginManager$e: Exception TooGenericExceptionCaught:ApiCaller.kt$e: Exception TooGenericExceptionCaught:ApkSignatureManager.kt$ApkSignatureManager$e: Exception TooGenericExceptionCaught:AppInfoFetchViewModel.kt$AppInfoFetchViewModel$e: Exception @@ -74,12 +68,10 @@ TooGenericExceptionCaught:EcloudRepository.kt$EcloudRepository$e: Exception TooGenericExceptionCaught:FileManager.kt$FileManager$e: Exception TooGenericExceptionCaught:GPlayHttpClient.kt$GPlayHttpClient$e: Exception - TooGenericExceptionCaught:GoogleLoginManager.kt$GoogleLoginManager$e: Exception TooGenericExceptionCaught:InstallWorkManager.kt$InstallWorkManager$e: Exception TooGenericExceptionCaught:LocaleChangedBroadcastReceiver.kt$LocaleChangedBroadcastReceiver$ex: Exception TooGenericExceptionCaught:NativeDeviceInfoProviderModule.kt$NativeDeviceInfoProviderModule$e: Exception TooGenericExceptionCaught:NetworkHandler.kt$e: Exception - TooGenericExceptionCaught:PlayStoreAuthenticator.kt$PlayStoreAuthenticator$e: Exception TooGenericExceptionCaught:PlayStoreRepository.kt$PlayStoreRepository$exception: Exception TooGenericExceptionCaught:PwaManager.kt$PwaManager$e: Exception TooGenericExceptionCaught:PwaPlayerStatusReceiver.kt$PwaPlayerStatusReceiver$e: Exception @@ -87,8 +79,6 @@ TooGenericExceptionCaught:UpdatesManagerImpl.kt$UpdatesManagerImpl$e: Exception TooGenericExceptionCaught:UpdatesWorker.kt$UpdatesWorker$e: Throwable TooGenericExceptionThrown:AnonymousLoginManager.kt$AnonymousLoginManager$throw Exception( "Error fetching Anonymous credentials\n" + "Network code: ${response.code}\n" + "Success: ${response.isSuccessful}" + response.errorString.run { if (isNotBlank()) "\nError message: $this" else "" } ) - TooGenericExceptionThrown:PlayStoreLoginWrapper.kt$PlayStoreLoginWrapper$throw Exception("Validation network code: ${response.code}") - TooGenericExceptionThrown:PlayStoreLoginWrapper.kt$PlayStoreLoginWrapper$throw Exception(error) TooManyFunctions:AppLoungePackageManager.kt$AppLoungePackageManager TooManyFunctions:AppManager.kt$AppManager TooManyFunctions:AppManagerImpl.kt$AppManagerImpl : AppManager diff --git a/app/src/main/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCase.kt b/app/src/main/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCase.kt new file mode 100644 index 000000000..8b6d49f68 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCase.kt @@ -0,0 +1,39 @@ +package foundation.e.apps.domain.login + +import foundation.e.apps.data.ecloud.EcloudRepository +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.exceptions.GPlayValidationException +import foundation.e.apps.utils.SystemInfoProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject + +class ReportFaultyTokenUseCase @Inject constructor( + private val ecloudRepository: EcloudRepository +) { + suspend operator fun invoke(authObjects: List) { + val gPlayAuthObject = authObjects.firstOrNull { it is AuthObject.GPlayAuth } as? AuthObject.GPlayAuth + if (gPlayAuthObject == null) { + return + } + + val result = gPlayAuthObject.result + val exception = result.exception + val shouldUploadFaultyToken = exception is GPlayValidationException + + if (shouldUploadFaultyToken) { + val email = result.otherPayload.toString() + val buildInfo = SystemInfoProvider.getAppBuildInfo() + withContext(Dispatchers.IO) { + ecloudRepository.uploadFaultyEmail(email, buildInfo) + } + } + + if (!shouldUploadFaultyToken) { + exception?.let { + Timber.e(it, "Login failed! message: ${it.localizedMessage}") + } + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index 68b2d7ed2..f0fd66815 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -39,7 +39,6 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.setupWithNavController -import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.exceptions.InternalException import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.snackbar.Snackbar @@ -50,9 +49,7 @@ import foundation.e.apps.contract.ParentalControlContract.COLUMN_LOGIN_TYPE import foundation.e.apps.data.Constants import foundation.e.apps.data.enums.User import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.login.core.AuthObject import foundation.e.apps.data.login.core.StoreType -import foundation.e.apps.data.login.exceptions.GPlayValidationException import foundation.e.apps.databinding.ActivityMainBinding import foundation.e.apps.install.updates.UpdatesNotifier import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment @@ -61,7 +58,6 @@ import foundation.e.apps.ui.purchase.AppPurchaseFragmentDirections import foundation.e.apps.ui.settings.SettingsFragment import foundation.e.apps.ui.setup.signin.SignInViewModel import foundation.e.apps.utils.ParentalControlAuthenticator -import foundation.e.apps.utils.SystemInfoProvider import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import kotlinx.coroutines.Dispatchers @@ -378,41 +374,22 @@ class MainActivity : AppCompatActivity() { } private fun observeAuthObjects(navController: NavController) { - loginViewModel.authObjects.distinctUntilChanged().observe(this) { - when { - it == null -> return@observe - it.isEmpty() -> { - // No auth type defined means user has not logged in yet - // Pop back stack to prevent showing TOSFragment on pressing back button. - navController.popBackStack() - navController.navigate(R.id.signInFragment) - if (viewModel.gPlayLoginRequested) viewModel.closeAfterLogin = true - return@observe - } - - else -> {} + viewModel.uiState.distinctUntilChanged().observe(this) { state -> + if (state.navigateToSignIn) { + navController.popBackStack() + navController.navigate(R.id.signInFragment) + viewModel.onNavigateToSignInHandled() } - val gPlayAuthObject = it.find { it is AuthObject.GPlayAuth } - - gPlayAuthObject?.result?.run { - if (isSuccess()) { - viewModel.gPlayAuthData = data as AuthData - } else if (exception is GPlayValidationException && viewModel.getUser() == User.ANONYMOUS) { - val email = otherPayload.toString() - viewModel.uploadFaultyTokenToEcloud( - email, - SystemInfoProvider.getAppBuildInfo() - ) - } else if (exception != null) { - Timber.e(exception, "Login failed! message: ${exception?.localizedMessage}") - } - } - - if (viewModel.closeAfterLogin && it.isNotEmpty() && it.all { it.result.isSuccess() }) { + if (state.finishAfterLogin) { + viewModel.onFinishAfterLoginHandled() finishAndRemoveTask() } } + + loginViewModel.authObjects.distinctUntilChanged().observe(this) { + viewModel.handleAuthObjects(it) + } } private suspend fun observeSuccessfulLogin() { diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index dce959d2d..399eeb87f 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -20,31 +20,30 @@ package foundation.e.apps.ui import android.content.Context import android.content.Intent -import android.net.ConnectivityManager import androidx.appcompat.app.AlertDialog import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.R import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.blockedApps.BlockedAppRepository -import foundation.e.apps.data.ecloud.EcloudRepository -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.enums.isInitialized -import foundation.e.apps.data.enums.isUnFiltered -import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.login.state.LoginState -import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository -import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.getSync +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.enums.isInitialized +import foundation.e.apps.data.enums.isUnFiltered +import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.state.LoginState +import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository +import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.getSync +import foundation.e.apps.domain.login.ReportFaultyTokenUseCase import foundation.e.apps.install.pkg.AppLoungePackageManager import foundation.e.apps.install.pkg.PwaManager import foundation.e.apps.install.workmanager.AppInstallProcessor @@ -60,13 +59,13 @@ class MainActivityViewModel @Inject constructor( private val appManagerWrapper: AppManagerWrapper, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PwaManager, - private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val gPlayContentRatingRepository: GPlayContentRatingRepository, private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, private val appInstallProcessor: AppInstallProcessor, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, -) : ViewModel() { + private val reportFaultyTokenUseCase: ReportFaultyTokenUseCase, +) : ViewModel() { init { updateAppWarningList() @@ -82,8 +81,6 @@ class MainActivityViewModel @Inject constructor( val purchaseDeclined: MutableLiveData = MutableLiveData() lateinit var internetConnection: LiveData - var gPlayAuthData = AuthData("", "") - // Downloads val downloadList = appManagerWrapper.getDownloadLiveList() private val _errorMessage = MutableLiveData() @@ -92,33 +89,78 @@ class MainActivityViewModel @Inject constructor( private val _errorMessageStringResource = MutableLiveData() val errorMessageStringResource: LiveData = _errorMessageStringResource + data class MainUiState( + val navigateToSignIn: Boolean = false, + val finishAfterLogin: Boolean = false + ) + + private val _uiState = MutableLiveData(MainUiState()) + val uiState: LiveData = _uiState + + private val initialUiState = MainUiState() + var gPlayLoginRequested = false var closeAfterLogin = false - lateinit var connectivityManager: ConnectivityManager - var shouldIgnoreSessionError = false fun getTocStatus(): Boolean { return appLoungeDataStore.tocStatus.getSync() } - fun getUser(): User { - return appLoungeDataStore.getUser() - } - - fun getLoginState(): LoginState { - return appLoungeDataStore.getLoginState() - } + fun getUser(): User { + return appLoungeDataStore.getUser() + } - fun getUserEmail(): String { - return appLoungeDataStore.emailData.getSync() + fun getLoginState(): LoginState { + return appLoungeDataStore.getLoginState() } - fun uploadFaultyTokenToEcloud(email: String, description: String = "") { - viewModelScope.launch { - ecloudRepository.uploadFaultyEmail(email, description) + fun handleAuthObjects(authObjects: List?) { + if (authObjects == null) { + return + } + + if (authObjects.isEmpty()) { + handleEmptyAuthObjects() + return + } + + val shouldFinishAfterLogin = shouldFinishAfterLogin(authObjects) + val user = getUser() + if (user == User.ANONYMOUS) { + viewModelScope.launch { + reportFaultyTokenUseCase(authObjects) + } + } + + if (shouldFinishAfterLogin) { + _uiState.value = _uiState.value?.copy(finishAfterLogin = true) + ?: initialUiState.copy(finishAfterLogin = true) + } + } + + fun onNavigateToSignInHandled() { + _uiState.value = _uiState.value?.copy(navigateToSignIn = false) + ?: initialUiState + } + + fun onFinishAfterLoginHandled() { + _uiState.value = _uiState.value?.copy(finishAfterLogin = false) + ?: initialUiState + } + + private fun handleEmptyAuthObjects() { + if (gPlayLoginRequested) { + closeAfterLogin = true } + + _uiState.value = _uiState.value?.copy(navigateToSignIn = true) + ?: initialUiState.copy(navigateToSignIn = true) + } + + private fun shouldFinishAfterLogin(authObjects: List): Boolean { + return closeAfterLogin && authObjects.all { it.result.isSuccess() } } /* @@ -140,10 +182,12 @@ class MainActivityViewModel @Inject constructor( * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/266 */ fun shouldShowPaidAppsSnackBar(app: Application): Boolean { - if (!app.isFree && gPlayAuthData.isAnonymous) { + val authData = appLoungeDataStore.getAuthData() + if (!app.isFree && authData.isAnonymous) { _errorMessageStringResource.value = R.string.paid_app_anonymous_message return true } + return false } @@ -205,12 +249,12 @@ class MainActivityViewModel @Inject constructor( suspend fun updateAwaitingForPurchasedApp(packageName: String): AppInstall? { val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) - gPlayAuthData.let { - if (!it.isAnonymous) { - appInstallProcessor.enqueueFusedDownload(fusedDownload) - return fusedDownload - } + val authData = appLoungeDataStore.getAuthData() + if (!authData.isAnonymous) { + appInstallProcessor.enqueueFusedDownload(fusedDownload) + return fusedDownload } + return null } @@ -233,17 +277,17 @@ class MainActivityViewModel @Inject constructor( fun updateStatusOfFusedApps( applicationList: List, - appInstallList: List - ) { - applicationList.forEach { - val downloadingItem = appInstallList.find { fusedDownload -> - fusedDownload.source == it.source && - (fusedDownload.packageName == it.package_name || fusedDownload.id == it._id) - } - it.status = - downloadingItem?.status ?: applicationRepository.getFusedAppInstallationStatus(it) - } - } + appInstallList: List + ) { + applicationList.forEach { + val downloadingItem = appInstallList.find { fusedDownload -> + fusedDownload.source == it.source && + (fusedDownload.packageName == it.package_name || fusedDownload.id == it._id) + } + it.status = + downloadingItem?.status ?: applicationRepository.getFusedAppInstallationStatus(it) + } + } fun updateAppWarningList() { viewModelScope.launch { diff --git a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt index 394d2824a..954fdb4fd 100644 --- a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt @@ -26,7 +26,6 @@ import android.view.View import android.widget.Toast import androidx.core.net.toUri import androidx.core.view.isVisible -import androidx.fragment.app.activityViewModels import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController import androidx.preference.CheckBoxPreference @@ -46,10 +45,11 @@ import foundation.e.apps.data.Stores import foundation.e.apps.data.application.UpdatesDao import foundation.e.apps.data.enums.Source import foundation.e.apps.data.enums.User +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.getSync import foundation.e.apps.databinding.CustomPreferenceBinding import foundation.e.apps.install.updates.UpdatesWorkManager import foundation.e.apps.ui.LoginViewModel -import foundation.e.apps.ui.MainActivityViewModel import foundation.e.apps.utils.SystemInfoProvider import timber.log.Timber import java.util.Locale @@ -59,7 +59,6 @@ import javax.inject.Inject class SettingsFragment : PreferenceFragmentCompat() { private var _binding: CustomPreferenceBinding? = null private val binding get() = _binding!! - private val mainActivityViewModel: MainActivityViewModel by activityViewModels() private var showAllApplications: CheckBoxPreference? = null private var showFOSSApplications: CheckBoxPreference? = null private var showPWAApplications: CheckBoxPreference? = null @@ -77,8 +76,11 @@ class SettingsFragment : PreferenceFragmentCompat() { @Inject lateinit var clipboardManager: ClipboardManager + @Inject + lateinit var appLoungeDataStore: AppLoungeDataStore + private val user by lazy { - mainActivityViewModel.getUser() + appLoungeDataStore.getUser() } private val allSourceCheckboxes by lazy { @@ -96,8 +98,10 @@ class SettingsFragment : PreferenceFragmentCompat() { val updateCheckInterval = preferenceManager.findPreference(getString(R.string.update_check_intervals)) + val updateCheckIntervalAnonymous = preferenceManager.findPreference(getString(R.string.update_check_intervals_anonymous)) + val updateChangeListener = { _: Preference, newValue: Any -> Timber.d("onCreatePreferences: updated Value: $newValue") context?.let { @@ -109,6 +113,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } true } + updateCheckInterval?.setOnPreferenceChangeListener(updateChangeListener) updateCheckIntervalAnonymous?.setOnPreferenceChangeListener(updateChangeListener) configureUpdatePreferencesForUser(updateCheckInterval, updateCheckIntervalAnonymous) @@ -129,6 +134,7 @@ class SettingsFragment : PreferenceFragmentCompat() { appVersionLabel, contents ) + Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() true @@ -188,8 +194,8 @@ class SettingsFragment : PreferenceFragmentCompat() { // This is useful if a user from older App Lounge updates to this version disableDependentCheckbox(onlyUnmeteredNetwork, autoInstallUpdate) - mainActivityViewModel.gPlayAuthData.let { authData -> - mainActivityViewModel.getUser().name.let { user -> + appLoungeDataStore.getAuthData().let { authData -> + appLoungeDataStore.getUser().name.let { user -> handleUser(user, authData) } } @@ -248,7 +254,7 @@ class SettingsFragment : PreferenceFragmentCompat() { User.GOOGLE.name -> { if (!authData.isAnonymous) { binding.accountType.text = authData.userProfile?.name - binding.email.text = mainActivityViewModel.getUserEmail() + binding.email.text = appLoungeDataStore.emailData.getSync() binding.avatar.load(authData.userProfile?.artwork?.url) } } diff --git a/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt b/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt new file mode 100644 index 000000000..f690fc253 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/domain/login/ReportFaultyTokenUseCaseTest.kt @@ -0,0 +1,64 @@ +package foundation.e.apps.domain.login + +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.exceptions.GPlayValidationException +import foundation.e.apps.data.ecloud.EcloudRepository +import foundation.e.apps.utils.SystemInfoProvider +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Test + +class ReportFaultyTokenUseCaseTest { + + private val ecloudRepository = mockk(relaxed = true) + private val reportFaultyTokenUseCase = ReportFaultyTokenUseCase(ecloudRepository) + + @After + fun tearDown() { + unmockkObject(SystemInfoProvider) + } + + @Test + fun handle_Gplay_returnsNullWhenNoGPlayAuthObject() = runTest { + val authObjects = listOf( + AuthObject.CleanApk(ResultSupreme.Success(Unit), User.ANONYMOUS) + ) + + reportFaultyTokenUseCase(authObjects) + } + + @Test + fun handle_Gplay_returnsAuthDataOnSuccess() = runTest { + val authData = AuthData("email") + val authObjects = listOf( + AuthObject.GPlayAuth(ResultSupreme.Success(authData), User.GOOGLE) + ) + + reportFaultyTokenUseCase(authObjects) + coVerify(exactly = 0) { ecloudRepository.uploadFaultyEmail(any(), any()) } + } + + @Test + fun handle_Gplay_uploadsFaultyTokenForAnonymousUser() = runTest { + mockkObject(SystemInfoProvider) + coEvery { ecloudRepository.uploadFaultyEmail("user@gmail.com", "build") } returns Unit + coEvery { SystemInfoProvider.getAppBuildInfo() } returns "build" + val exception = GPlayValidationException("invalid", User.ANONYMOUS, 401) + val result = ResultSupreme.Error("invalid", exception) + result.otherPayload = "user@gmail.com" + val authObjects = listOf( + AuthObject.GPlayAuth(result, User.ANONYMOUS) + ) + + reportFaultyTokenUseCase(authObjects) + coVerify(exactly = 1) { ecloudRepository.uploadFaultyEmail("user@gmail.com", "build") } + } +} -- GitLab