diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 1e99001e62..81205292a5 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.WebExtensionState import mozilla.components.concept.engine.EngineSession @@ -126,7 +127,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { private var isVisuallyComplete = false - private var privateNotificationObserver: PrivateNotificationFeature? = null + private var privateNotificationObserver: PrivateNotificationFeature? = + null private var isToolbarInflated = false @@ -192,14 +194,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { it.start() } - if (isActivityColdStarted(intent, savedInstanceState)) { - externalSourceIntentProcessors.any { + if (isActivityColdStarted( + intent, + savedInstanceState + ) && !externalSourceIntentProcessors.any { it.process( intent, navHost.navController, this.intent ) } + ) { + navigateToBrowserOnColdStart() } Performance.processIntentIfPerformanceTest(intent, this) @@ -240,7 +246,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { StartupTimeline.onActivityCreateEndHome(this) // DO NOT MOVE ANYTHING BELOW HERE. } - protected open fun startupTelemetryOnCreateCalled(safeIntent: SafeIntent, hasSavedInstanceState: Boolean) { + protected open fun startupTelemetryOnCreateCalled( + safeIntent: SafeIntent, + hasSavedInstanceState: Boolean + ) { components.appStartupTelemetry.onHomeActivityOnCreate( safeIntent, hasSavedInstanceState, @@ -322,6 +331,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } final override fun onPause() { + // We should return to the browser if there were normal tabs when we left the app + settings().shouldReturnToBrowser = + components.core.store.state.getNormalOrPrivateTabs(private = false).isNotEmpty() + if (settings().lastKnownMode.isPrivate) { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } @@ -763,6 +776,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity { } } + open fun navigateToBrowserOnColdStart() { + // Normal tabs + cold start -> Should go back to browser if we had any tabs open when we left last + // except for PBM + Cold Start there won't be any tabs since they're evicted so we never will navigate + if (settings().shouldReturnToBrowser && !browsingModeManager.mode.isPrivate) { + openToBrowser(BrowserDirection.FromGlobal, null) + } + } + override fun attachBaseContext(base: Context) { base.components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { super.attachBaseContext(base) diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 9c6e9aa5d1..17997e65ae 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -41,6 +42,7 @@ import mozilla.components.browser.session.SessionManager import mozilla.components.browser.state.action.ContentAction import mozilla.components.browser.state.selector.findTab import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab +import mozilla.components.browser.state.selector.getNormalOrPrivateTabs import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.content.DownloadState import mozilla.components.browser.state.store.BrowserStore @@ -189,6 +191,28 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session val view = inflater.inflate(R.layout.fragment_browser, container, false) val activity = activity as HomeActivity + components = requireComponents + + if (customTabSessionId == null) { + // Once tab restoration is complete, if there are no tabs to show in the browser, go home + components.core.store.flowScoped(viewLifecycleOwner) { flow -> + flow.map { state -> state.restoreComplete } + .ifChanged() + .collect { restored -> + if (restored) { + val tabs = + components.core.store.state.getNormalOrPrivateTabs( + activity.browsingModeManager.mode.isPrivate + ) + if (tabs.isEmpty()) findNavController().popBackStack( + R.id.homeFragment, + false + ) + } + } + } + } + activity.themeManager.applyStatusBarTheme(activity) browserFragmentStore = StoreProvider.get(this) { @@ -197,8 +221,6 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session ) } - components = requireComponents - return view } diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 3ce27cd479..8a8d3a57f6 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -21,6 +21,7 @@ import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.engine.EngineMiddleware import mozilla.components.browser.session.storage.SessionStorage import mozilla.components.browser.session.undo.UndoMiddleware +import mozilla.components.browser.state.action.RestoreCompleteAction import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.store.BrowserStore @@ -231,6 +232,8 @@ class Core( } } } + + store.dispatch(RestoreCompleteAction) } WebNotificationFeature( diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt index bd27a18987..3149204803 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt @@ -46,6 +46,10 @@ open class ExternalAppBrowserActivity : HomeActivity() { ) } + override fun navigateToBrowserOnColdStart() { + // No-op for external app + } + override fun getNavDirections( from: BrowserDirection, customTabSessionId: String? diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index f9dcc8faa7..33b1b2b42c 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -182,6 +182,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { true ) + var shouldReturnToBrowser by booleanPreference( + appContext.getString(R.string.pref_key_return_to_browser), + false + ) + // If any of the prefs have been modified, quit displaying the fenix moved tip fun shouldDisplayFenixMovingTip(): Boolean = preferences.getBoolean( diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 4c5266a407..63ec9a704a 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -235,4 +235,6 @@ pref_key_show_grid_view_tabs_settings pref_key_camera_permissions_needed + + pref_key_return_to_browser diff --git a/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt b/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt index 6f5c9634cb..9d83d53603 100644 --- a/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/HomeActivityTest.kt @@ -7,7 +7,9 @@ package org.mozilla.fenix import android.content.Intent import android.os.Bundle import io.mockk.every +import io.mockk.mockk import io.mockk.spyk +import io.mockk.verify import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.utils.toSafeIntent import org.junit.Assert.assertEquals @@ -20,9 +22,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mozilla.fenix.HomeActivity.Companion.PRIVATE_BROWSING_MODE import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings @RunWith(FenixRobolectricTestRunner::class) class HomeActivityTest { @@ -32,8 +37,6 @@ class HomeActivityTest { @Before fun setup() { activity = spyk(HomeActivity()) - - every { activity.applicationContext } returns testContext } @Test @@ -52,6 +55,7 @@ class HomeActivityTest { @Test fun `getModeFromIntentOrLastKnown returns mode from settings when intent does not set`() { + every { activity.applicationContext } returns testContext testContext.settings().lastKnownMode = BrowsingMode.Private assertEquals(testContext.settings().lastKnownMode, activity.getModeFromIntentOrLastKnown(null)) @@ -87,6 +91,38 @@ class HomeActivityTest { assertFalse(activity.isActivityColdStarted(startingIntent, null)) } + @Test + fun `navigateToBrowserOnColdStart in normal mode navigates to browser`() { + val browsingModeManager: BrowsingModeManager = mockk() + every { browsingModeManager.mode } returns BrowsingMode.Normal + + val settings: Settings = mockk() + every { settings.shouldReturnToBrowser } returns true + every { activity.components.settings.shouldReturnToBrowser } returns true + every { activity.openToBrowser(any(), any()) } returns Unit + + activity.browsingModeManager = browsingModeManager + activity.navigateToBrowserOnColdStart() + + verify(exactly = 1) { activity.openToBrowser(BrowserDirection.FromGlobal, null) } + } + + @Test + fun `navigateToBrowserOnColdStart in private mode does not navigate to browser`() { + val browsingModeManager: BrowsingModeManager = mockk() + every { browsingModeManager.mode } returns BrowsingMode.Private + + val settings: Settings = mockk() + every { settings.shouldReturnToBrowser } returns true + every { activity.components.settings.shouldReturnToBrowser } returns true + every { activity.openToBrowser(any(), any()) } returns Unit + + activity.browsingModeManager = browsingModeManager + activity.navigateToBrowserOnColdStart() + + verify(exactly = 0) { activity.openToBrowser(BrowserDirection.FromGlobal, null) } + } + @Test fun `isActivityColdStarted returns false for null savedInstanceState and not launched from history`() { val startingIntent = Intent().apply { diff --git a/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt index a0b117af87..b4f3c02a99 100644 --- a/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt +++ b/app/src/test/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivityTest.kt @@ -17,7 +17,11 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.utils.Settings class ExternalAppBrowserActivityTest { @@ -37,6 +41,23 @@ class ExternalAppBrowserActivityTest { assertEquals(Event.OpenedApp.Source.CUSTOM_TAB, activity.getIntentSource(otherIntent)) } + @Test + fun `navigateToBrowserOnColdStart does nothing for external app browser activity`() { + val activity = spyk(ExternalAppBrowserActivity()) + val browsingModeManager: BrowsingModeManager = mockk() + every { browsingModeManager.mode } returns BrowsingMode.Normal + + val settings: Settings = mockk() + every { settings.shouldReturnToBrowser } returns true + every { activity.components.settings.shouldReturnToBrowser } returns true + every { activity.openToBrowser(any(), any()) } returns Unit + + activity.browsingModeManager = browsingModeManager + activity.navigateToBrowserOnColdStart() + + verify(exactly = 0) { activity.openToBrowser(BrowserDirection.FromGlobal, null) } + } + @Test fun `getNavDirections finishes activity if session ID is null`() { val activity = spyk(object : ExternalAppBrowserActivity() { diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index 0856ac71ac..d6b3ef2c64 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -53,6 +53,19 @@ class SettingsTest { assertTrue(settings.openLinksInAPrivateTab) } + @Test + fun shouldReturnToBrowser() { + // When just created + // Then + assertFalse(settings.shouldReturnToBrowser) + + // When + settings.shouldReturnToBrowser = true + + // Then + assertTrue(settings.shouldReturnToBrowser) + } + @Test fun clearDataOnQuit() { // When just created