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

Commit 61108d10 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

Add functionality to ignore refresh session popup during an app session

Added back press handling for Android 13, Android 12, and lower. Implemented check for session refresh on app start.

Due to a change in Android 12 behavior (https://is.gd/7HzORm), the onCreate() and onDestroy() activity lifecycle callbacks will not be triggered anymore while using back navigation. Hence, onBackPressed() has to be overridden to detect if the app session is going to be closed by the user using back navigation.

In Android 13+, onBackPressed() will not be invoked. Hence, OnBackInvokedCallback is used to detect back navigation by the user.
parent de5c63cd
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -254,4 +254,8 @@ dependencies {

    // elib
    implementation 'foundation.e:elib:0.0.1-alpha11'

    // androidx.activity
    def activity_version = "1.6.1"
    implementation("androidx.activity:activity-ktx:$activity_version")
}
+19 −1
Original line number Diff line number Diff line
<?xml version="1.0" ?>
<!--
  ~ Copyright (C) 2021-2024 MURENA SAS
  ~
  ~ 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/>.
  ~
  -->

<SmellBaseline>
  <ManuallySuppressedIssues></ManuallySuppressedIssues>
  <CurrentIssues>
@@ -19,7 +37,7 @@
    <ID>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, )</ID>
    <ID>LongParameterList:EglExtensionProvider.kt$EglExtensionProvider$( egl10: EGL10, eglDisplay: EGLDisplay, eglConfig: EGLConfig?, ai: IntArray, ai1: IntArray?, set: MutableSet&lt;String&gt; )</ID>
    <ID>LongParameterList:FusedManagerImpl.kt$FusedManagerImpl$( @Named("cacheDir") private val cacheDir: String, private val downloadManager: DownloadManager, private val notificationManager: NotificationManager, private val fusedDownloadRepository: FusedDownloadRepository, 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 )</ID>
    <ID>LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val fusedManagerRepository: FusedManagerRepository, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PWAManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val appInstallProcessor: AppInstallProcessor )</ID>
    <ID>LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val fusedManagerRepository: FusedManagerRepository, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PWAManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val appInstallProcessor: AppInstallProcessor, private val sessionPreference: SessionPreference )</ID>
    <ID>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, )</ID>
    <ID>LongParameterList:UpdatesWorker.kt$UpdatesWorker$( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val updatesManagerRepository: UpdatesManagerRepository, private val dataStoreManager: DataStoreManager, private val authenticatorRepository: AuthenticatorRepository, private val appInstallProcessor: AppInstallProcessor, private val blockedAppRepository: BlockedAppRepository, )</ID>
    <ID>MagicNumber:AnonymousLoginManager.kt$AnonymousLoginManager$200</ID>
+21 −1
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2021-2024 MURENA SAS
  ~
  ~ 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/>.
  ~
  -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

@@ -42,7 +60,9 @@
        android:restoreNeedsApplication="true"
        android:supportsRtl="true"
        android:theme="@style/Theme.Apps"
        android:usesCleartextTraffic="true">
        android:enableOnBackInvokedCallback="true"
        android:usesCleartextTraffic="true"
        tools:targetApi="tiramisu">

        <activity
            android:name=".MainActivity"
+5 −5
Original line number Diff line number Diff line
/*
 * Apps  Quickly and easily install Android apps onto your device!
 * Copyright (C) 2021  E FOUNDATION
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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
@@ -14,6 +13,7 @@
 *
 * 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
@@ -29,8 +29,8 @@ import dagger.hilt.android.HiltAndroidApp
import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP
import foundation.e.apps.data.preference.AppLoungeDataStore
import foundation.e.apps.data.preference.AppLoungePreference
import foundation.e.apps.install.pkg.PkgManagerBR
import foundation.e.apps.install.pkg.AppLoungePackageManager
import foundation.e.apps.install.pkg.PkgManagerBR
import foundation.e.apps.install.updates.UpdatesWorkManager
import foundation.e.apps.install.workmanager.InstallWorkManager
import foundation.e.apps.ui.setup.tos.TOS_VERSION
@@ -38,13 +38,13 @@ import foundation.e.apps.utils.CustomUncaughtExceptionHandler
import foundation.e.lib.telemetry.Telemetry
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
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
+75 −13
Original line number Diff line number Diff line
/*
 * Apps  Quickly and easily install Android apps onto your device!
 * Copyright (C) 2021  E FOUNDATION
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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
@@ -14,13 +13,16 @@
 *
 * 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 android.os.Build
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.view.View
import android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
@@ -43,7 +45,6 @@ import foundation.e.apps.data.login.AuthObject
import foundation.e.apps.data.login.LoginViewModel
import foundation.e.apps.data.login.PlayStoreAuthenticator
import foundation.e.apps.data.login.exceptions.GPlayValidationException
import foundation.e.apps.data.preference.AppLoungePreference
import foundation.e.apps.databinding.ActivityMainBinding
import foundation.e.apps.install.updates.UpdatesNotifier
import foundation.e.apps.ui.MainActivityViewModel
@@ -59,23 +60,25 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var signInViewModel: SignInViewModel
    private lateinit var loginViewModel: LoginViewModel
    private lateinit var binding: ActivityMainBinding
    private val TAG = MainActivity::class.java.simpleName
    private lateinit var viewModel: MainActivityViewModel

    @Inject
    lateinit var appLoungePreference: AppLoungePreference
    companion object {
        private val TAG = MainActivity::class.java.simpleName
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)

        setupBackPressHandlingForTiramisuAndAbove()

        setContentView(binding.root)

        val (bottomNavigationView, navController) = setupBootomNav()
@@ -88,7 +91,7 @@ class MainActivity : AppCompatActivity() {
            bottomNavigationView.selectedItemId = R.id.updatesFragment
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        if (VERSION.SDK_INT >= VERSION_CODES.O) {
            viewModel.createNotificationChannels()
        }

@@ -120,6 +123,54 @@ class MainActivity : AppCompatActivity() {
        observeEvents()
    }

    override fun onStart() {
        super.onStart()
        checkSessionRefresh()
    }

    private fun checkSessionRefresh() {
        if (viewModel.shouldRefreshSession()) {
            refreshSession()
        }
    }

    private fun refreshSession() {
        loginViewModel.startLoginFlow(listOf(PlayStoreAuthenticator::class.java.simpleName))
    }

    // In Android 12 (API level 32) and lower, onBackPressed is always called,
    // regardless of any registered instances of OnBackPressedCallback.
    // https://developer.android.com/guide/navigation/navigation-custom-back#onbackpressed
    @Deprecated("Deprecated in Java")
    override fun onBackPressed() {
        if (isInitialScreen()) {
            resetIgnoreStatusForSessionRefresh()
        }
        super.onBackPressed()
    }

    private fun isInitialScreen(): Boolean {
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment

        val navController = navHostFragment.navController

        return navController.currentDestination?.id == navController.graph.startDestinationId
    }

    private fun resetIgnoreStatusForSessionRefresh() {
        viewModel.updateIgnoreRefreshPreference(ignore = false)
    }

    @Suppress("DEPRECATION")
    private fun setupBackPressHandlingForTiramisuAndAbove() {
        if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
            onBackInvokedDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT) {
                resetIgnoreStatusForSessionRefresh()
                super.onBackPressed() // Deprecated for Android 13+
            }
        }
    }

    private fun setupNavigations(navController: NavController) {
        val navOptions = NavOptions.Builder()
@@ -355,14 +406,25 @@ class MainActivity : AppCompatActivity() {
        EventBus.events.filter { appEvent ->
            appEvent is AppEvent.TooManyRequests
        }.collectLatest {
            val shouldShowDialog = viewModel.shouldRefreshSession()
            if (shouldShowDialog) {
                binding.sessionErrorLayout.visibility = View.VISIBLE
            binding.retrySessionButton.setOnClickListener {
                binding.sessionErrorLayout.visibility = View.GONE
                loginViewModel.startLoginFlow(listOf(PlayStoreAuthenticator::class.java.simpleName))
                binding.retrySessionButton.setOnClickListener { onRefreshSessionClick() }
                binding.ignoreSessionButton.setOnClickListener { onIgnoreSessionClick() }
            }
        }
    }

    private fun onIgnoreSessionClick() {
        viewModel.updateIgnoreRefreshPreference(true)
        binding.sessionErrorLayout.visibility = View.GONE
    }

    private fun onRefreshSessionClick() {
        binding.sessionErrorLayout.visibility = View.GONE
        refreshSession()
    }

    private fun setupBottomNavItemSelectedListener(
        bottomNavigationView: BottomNavigationView,
        navHostFragment: NavHostFragment,
Loading