From 5784c271144720a0bccd89e2890a29fec11e096d Mon Sep 17 00:00:00 2001 From: Sebastian Kaspari Date: Mon, 11 Jan 2021 15:15:39 +0100 Subject: [PATCH] Closes #4257: Migrate feature-customtabs from browser-session to browser-state. --- .../state/state/CustomTabSessionState.kt | 6 +- components/feature/customtabs/build.gradle | 6 +- .../customtabs/CustomTabIntentProcessor.kt | 18 +- .../customtabs/CustomTabsToolbarFeature.kt | 82 +- .../feature/CustomTabSessionTitleObserver.kt | 38 +- .../CustomTabIntentProcessorTest.kt | 60 +- .../CustomTabsToolbarFeatureTest.kt | 959 +++++++++++------- .../CustomTabSessionTitleObserverTest.kt | 43 +- .../feature/tabs/CustomTabsUseCases.kt | 69 ++ .../samples/browser/DefaultComponents.kt | 5 +- .../browser/ExternalAppBrowserFragment.kt | 3 +- 11 files changed, 814 insertions(+), 475 deletions(-) create mode 100644 components/feature/tabs/src/main/java/mozilla/components/feature/tabs/CustomTabsUseCases.kt diff --git a/components/browser/state/src/main/java/mozilla/components/browser/state/state/CustomTabSessionState.kt b/components/browser/state/src/main/java/mozilla/components/browser/state/state/CustomTabSessionState.kt index a36f0eb1c72..91086838cb2 100644 --- a/components/browser/state/src/main/java/mozilla/components/browser/state/state/CustomTabSessionState.kt +++ b/components/browser/state/src/main/java/mozilla/components/browser/state/state/CustomTabSessionState.kt @@ -58,6 +58,7 @@ fun createCustomTab( url: String, id: String = UUID.randomUUID().toString(), config: CustomTabConfig = CustomTabConfig(), + title: String = "", contextId: String? = null, engineSession: EngineSession? = null, mediaSessionState: MediaSessionState? = null, @@ -65,7 +66,10 @@ fun createCustomTab( ): CustomTabSessionState { return CustomTabSessionState( id = id, - content = ContentState(url), + content = ContentState( + url = url, + title = title + ), config = config, mediaSessionState = mediaSessionState, contextId = contextId, diff --git a/components/feature/customtabs/build.gradle b/components/feature/customtabs/build.gradle index cb231dd5d12..67818284a60 100644 --- a/components/feature/customtabs/build.gradle +++ b/components/feature/customtabs/build.gradle @@ -29,13 +29,14 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { dependencies { implementation project(':browser-menu') - implementation project(':browser-session') + implementation project(':browser-state') implementation project(':browser-toolbar') implementation project(':concept-engine') implementation project(':concept-fetch') implementation project(':concept-menu') implementation project(':feature-session') implementation project(':feature-intent') + implementation project(':feature-tabs') implementation project(':service-digitalassetlinks') implementation project(':support-base') implementation project(':support-ktx') @@ -49,7 +50,8 @@ dependencies { api Dependencies.androidx_browser testImplementation project(':support-test') - + testImplementation project(':support-test-libstate') + testImplementation project(':browser-session') testImplementation Dependencies.androidx_test_core testImplementation Dependencies.androidx_test_junit testImplementation Dependencies.testing_coroutines diff --git a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabIntentProcessor.kt b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabIntentProcessor.kt index 9aa2a7cce7f..e49c0e1f756 100644 --- a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabIntentProcessor.kt +++ b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabIntentProcessor.kt @@ -9,13 +9,9 @@ import android.content.Intent.ACTION_VIEW import android.content.res.Resources import android.provider.Browser import androidx.annotation.VisibleForTesting -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import mozilla.components.browser.state.state.SessionState -import mozilla.components.concept.engine.EngineSession import mozilla.components.feature.intent.ext.putSessionId import mozilla.components.feature.intent.processing.IntentProcessor -import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.tabs.CustomTabsUseCases import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.toSafeIntent @@ -23,8 +19,7 @@ import mozilla.components.support.utils.toSafeIntent * Processor for intents which trigger actions related to custom tabs. */ class CustomTabIntentProcessor( - private val sessionManager: SessionManager, - private val loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase, + private val addCustomTabUseCase: CustomTabsUseCases.AddCustomTabUseCase, private val resources: Resources, private val isPrivate: Boolean = false ) : IntentProcessor { @@ -58,12 +53,9 @@ class CustomTabIntentProcessor( val url = safeIntent.dataString return if (!url.isNullOrEmpty() && matches(intent)) { - val session = Session(url, private = isPrivate, source = SessionState.Source.CUSTOM_TAB) - session.customTabConfig = createCustomTabConfigFromIntent(intent, resources) - - sessionManager.add(session) - loadUrlUseCase(url, session.id, EngineSession.LoadUrlFlags.external(), getAdditionalHeaders(safeIntent)) - intent.putSessionId(session.id) + val config = createCustomTabConfigFromIntent(intent, resources) + val customTabId = addCustomTabUseCase(url, config, isPrivate, getAdditionalHeaders(safeIntent)) + intent.putSessionId(customTabId) true } else { diff --git a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeature.kt b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeature.kt index db81c6e2da4..3fe1df1d041 100644 --- a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeature.kt +++ b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeature.kt @@ -12,24 +12,31 @@ import androidx.annotation.ColorInt import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.graphics.drawable.toDrawable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.mapNotNull import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.BrowserMenuItem import mozilla.components.browser.menu.item.SimpleBrowserMenuItem -import mozilla.components.browser.session.Session -import mozilla.components.browser.session.SessionManager -import mozilla.components.browser.session.runWithSession +import mozilla.components.browser.state.selector.findCustomTab import mozilla.components.browser.state.state.CustomTabActionButtonConfig import mozilla.components.browser.state.state.CustomTabMenuItem +import mozilla.components.browser.state.state.CustomTabSessionState +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.toolbar.Toolbar import mozilla.components.feature.customtabs.feature.CustomTabSessionTitleObserver import mozilla.components.feature.customtabs.menu.sendWithUrl +import mozilla.components.feature.tabs.CustomTabsUseCases +import mozilla.components.lib.state.ext.flowScoped import mozilla.components.support.base.feature.LifecycleAwareFeature import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.ktx.android.content.share import mozilla.components.support.ktx.android.util.dpToPx import mozilla.components.support.ktx.android.view.setNavigationBarTheme import mozilla.components.support.ktx.android.view.setStatusBarTheme +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged import mozilla.components.support.utils.ColorUtils.getReadableTextColor /** @@ -42,34 +49,41 @@ import mozilla.components.support.utils.ColorUtils.getReadableTextColor * @param shareListener Invoked when the share button is pressed. * @param closeListener Invoked when the close button is pressed. */ -@Suppress("LargeClass", "Deprecation") -// SessionManager observable is deprecated. This feature needs to be migrated to browser-state: -// https://github.com/mozilla-mobile/android-components/issues/4257 +@Suppress("LargeClass", "LongParameterList") class CustomTabsToolbarFeature( - private val sessionManager: SessionManager, + private val store: BrowserStore, private val toolbar: BrowserToolbar, private val sessionId: String? = null, + private val useCases: CustomTabsUseCases, private val menuBuilder: BrowserMenuBuilder? = null, private val menuItemIndex: Int = menuBuilder?.items?.size ?: 0, private val window: Window? = null, private val shareListener: (() -> Unit)? = null, private val closeListener: () -> Unit ) : LifecycleAwareFeature, UserInteractionHandler { - - private val sessionObserver = CustomTabSessionTitleObserver(toolbar) + private var initialized: Boolean = false + private val titleObserver = CustomTabSessionTitleObserver(toolbar) private val context get() = toolbar.context - internal var initialized = false internal var readableColor = Color.WHITE + private var scope: CoroutineScope? = null /** * Initializes the feature and registers the [CustomTabSessionTitleObserver]. */ override fun start() { - if (initialized) return + val tabId = sessionId ?: return + val tab = store.state.findCustomTab(tabId) ?: return + + scope = store.flowScoped { flow -> + flow + .mapNotNull { state -> state.findCustomTab(tabId) } + .ifAnyChanged { tab -> arrayOf(tab.content.title, tab.content.url) } + .collect { tab -> titleObserver.onTab(tab) } + } - initialized = sessionManager.runWithSession(sessionId) { - it.register(sessionObserver) - initialize(it) + if (!initialized) { + initialized = true + init(tab) } } @@ -77,15 +91,12 @@ class CustomTabsToolbarFeature( * Unregisters the [CustomTabSessionTitleObserver]. */ override fun stop() { - sessionManager.runWithSession(sessionId) { - it.unregister(sessionObserver) - true - } + scope?.cancel() } @VisibleForTesting - internal fun initialize(session: Session): Boolean { - val config = session.customTabConfig ?: return false + internal fun init(tab: CustomTabSessionState) { + val config = tab.config // Don't allow clickable toolbar so a custom tab can't switch to edit mode. toolbar.display.onUrlClicked = { false } @@ -100,23 +111,21 @@ class CustomTabsToolbarFeature( // Add navigation close action if (config.showCloseButton) { - addCloseButton(session, config.closeButtonIcon) + addCloseButton(tab, config.closeButtonIcon) } // Add action button - addActionButton(session, config.actionButtonConfig) + addActionButton(tab, config.actionButtonConfig) // Show share button if (config.showShareMenuItem) { - addShareButton(session) + addShareButton(tab) } // Add menu items if (config.menuItems.isNotEmpty() || menuBuilder?.items?.isNotEmpty() == true) { - addMenuItems(session, config.menuItems, menuItemIndex) + addMenuItems(tab, config.menuItems, menuItemIndex) } - - return true } @VisibleForTesting @@ -145,7 +154,7 @@ class CustomTabsToolbarFeature( * When clicked, it calls [closeListener]. */ @VisibleForTesting - internal fun addCloseButton(session: Session, bitmap: Bitmap?) { + internal fun addCloseButton(tab: CustomTabSessionState, bitmap: Bitmap?) { val drawableIcon = bitmap?.toDrawable(context.resources) ?: getDrawable(context, R.drawable.mozac_ic_close)!!.mutate() @@ -156,7 +165,7 @@ class CustomTabsToolbarFeature( context.getString(R.string.mozac_feature_customtabs_exit_button) ) { emitCloseFact() - sessionManager.remove(session) + useCases.remove(tab.id) closeListener.invoke() } toolbar.addNavigationAction(button) @@ -167,7 +176,7 @@ class CustomTabsToolbarFeature( * When clicked, it activates the corresponding [PendingIntent]. */ @VisibleForTesting - internal fun addActionButton(session: Session, buttonConfig: CustomTabActionButtonConfig?) { + internal fun addActionButton(tab: CustomTabSessionState, buttonConfig: CustomTabActionButtonConfig?) { buttonConfig?.let { config -> val drawableIcon = Bitmap.createScaledBitmap( config.icon, @@ -184,7 +193,7 @@ class CustomTabsToolbarFeature( config.description ) { emitActionButtonFact() - config.pendingIntent.sendWithUrl(context, session.url) + config.pendingIntent.sendWithUrl(context, tab.content.url) } toolbar.addBrowserAction(button) @@ -196,7 +205,7 @@ class CustomTabsToolbarFeature( * When clicked, it activates [shareListener] and defaults to the [share] KTX helper. */ @VisibleForTesting - internal fun addShareButton(session: Session) { + internal fun addShareButton(tab: CustomTabSessionState) { val drawableIcon = getDrawable(context, R.drawable.mozac_ic_share)!! drawableIcon.setTint(readableColor) @@ -204,7 +213,7 @@ class CustomTabsToolbarFeature( drawableIcon, context.getString(R.string.mozac_feature_customtabs_share_link) ) { - val listener = shareListener ?: { context.share(session.url) } + val listener = shareListener ?: { context.share(tab.content.url) } emitActionButtonFact() listener.invoke() } @@ -217,13 +226,13 @@ class CustomTabsToolbarFeature( */ @VisibleForTesting internal fun addMenuItems( - session: Session, + tab: CustomTabSessionState, menuItems: List, index: Int ) { menuItems.map { item -> SimpleBrowserMenuItem(item.name) { - item.pendingIntent.sendWithUrl(context, session.url) + item.pendingIntent.sendWithUrl(context, tab.content.url) } }.also { items -> val combinedItems = menuBuilder?.let { builder -> @@ -253,10 +262,11 @@ class CustomTabsToolbarFeature( return if (!initialized) { false } else { - sessionManager.runWithSession(sessionId) { + if (sessionId != null && useCases.remove(sessionId)) { closeListener.invoke() - remove(it) true + } else { + false } } } diff --git a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserver.kt b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserver.kt index 497fe016997..d87218efb89 100644 --- a/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserver.kt +++ b/components/feature/customtabs/src/main/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserver.kt @@ -4,7 +4,7 @@ package mozilla.components.feature.customtabs.feature -import mozilla.components.browser.session.Session +import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.concept.toolbar.Toolbar /** @@ -12,25 +12,39 @@ import mozilla.components.concept.toolbar.Toolbar */ class CustomTabSessionTitleObserver( private val toolbar: Toolbar -) : Session.Observer { - private var receivedTitle = false +) { + private var url: String? = null + private var title: String? = null + private var showedTitle = false - override fun onUrlChanged(session: Session, url: String) { + internal fun onTab(tab: CustomTabSessionState) { + if (tab.content.title != title) { + onTitleChanged(tab) + title = tab.content.title + } + + if (tab.content.url != url) { + onUrlChanged(tab) + url = tab.content.url + } + } + + private fun onUrlChanged(tab: CustomTabSessionState) { // If we showed a title once in a custom tab then we are going to continue displaying // a title (to avoid the layout bouncing around). However if no title is available then // we just use the URL. - if (receivedTitle && session.title.isEmpty()) { - toolbar.title = url + if (showedTitle && tab.content.title.isEmpty()) { + toolbar.title = tab.content.url } } - override fun onTitleChanged(session: Session, title: String) { - if (title.isNotEmpty()) { - toolbar.title = title - receivedTitle = true - } else if (receivedTitle) { + private fun onTitleChanged(tab: CustomTabSessionState) { + if (tab.content.title.isNotEmpty()) { + toolbar.title = tab.content.title + showedTitle = true + } else if (showedTitle) { // See comment in OnUrlChanged(). - toolbar.title = session.url + toolbar.title = tab.content.url } } } diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabIntentProcessorTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabIntentProcessorTest.kt index 30a4b7ce34d..85af506dd20 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabIntentProcessorTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabIntentProcessorTest.kt @@ -12,15 +12,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.action.BrowserAction import mozilla.components.browser.state.action.EngineAction +import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.SessionState.Source import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession.LoadUrlFlags import mozilla.components.feature.intent.ext.EXTRA_SESSION_ID import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.tabs.CustomTabsUseCases import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor +import mozilla.components.support.test.middleware.CaptureActionsMiddleware import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.whenever @@ -38,16 +42,17 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) @ExperimentalCoroutinesApi class CustomTabIntentProcessorTest { - @Test fun processCustomTabIntentWithDefaultHandlers() { - val store: BrowserStore = mock() + val middleware = CaptureActionsMiddleware() + val store = BrowserStore(middleware = listOf(middleware)) val engine: Engine = mock() - val sessionManager = spy(SessionManager(engine)) + val sessionManager = spy(SessionManager(engine, store)) val useCases = SessionUseCases(store, sessionManager) + val customTabsUseCases = CustomTabsUseCases(sessionManager, useCases.loadUrl) val handler = - CustomTabIntentProcessor(sessionManager, useCases.loadUrl, testContext.resources) + CustomTabIntentProcessor(customTabsUseCases.add, testContext.resources) val intent = mock() whenever(intent.action).thenReturn(Intent.ACTION_VIEW) @@ -59,9 +64,13 @@ class CustomTabIntentProcessorTest { val sessionCaptor = argumentCaptor() verify(sessionManager).add(sessionCaptor.capture(), eq(false), eq(null), eq(null), eq(null)) - verify(store).dispatch(EngineAction.LoadUrlAction( - sessionCaptor.value.id, "http://mozilla.org", LoadUrlFlags.external()) - ) + + middleware.assertFirstAction(EngineAction.LoadUrlAction::class) { action -> + assertEquals(sessionCaptor.value.id, action.sessionId) + assertEquals("http://mozilla.org", action.url) + assertEquals(LoadUrlFlags.external(), action.flags) + } + verify(intent).putExtra(eq(EXTRA_SESSION_ID), any()) val customTabSession = sessionManager.all[0] @@ -74,13 +83,15 @@ class CustomTabIntentProcessorTest { @Test fun processCustomTabIntentWithAdditionalHeaders() { - val store: BrowserStore = mock() + val middleware = CaptureActionsMiddleware() + val store = BrowserStore(middleware = listOf(middleware)) val engine: Engine = mock() - val sessionManager = spy(SessionManager(engine)) + val sessionManager = spy(SessionManager(engine, store)) val useCases = SessionUseCases(store, sessionManager) + val customTabsUseCases = CustomTabsUseCases(sessionManager, useCases.loadUrl) val handler = - CustomTabIntentProcessor(sessionManager, useCases.loadUrl, testContext.resources) + CustomTabIntentProcessor(customTabsUseCases.add, testContext.resources) val intent = mock() whenever(intent.action).thenReturn(Intent.ACTION_VIEW) @@ -98,9 +109,14 @@ class CustomTabIntentProcessorTest { val sessionCaptor = argumentCaptor() verify(sessionManager).add(sessionCaptor.capture(), eq(false), eq(null), eq(null), eq(null)) - verify(store).dispatch(EngineAction.LoadUrlAction( - sessionCaptor.value.id, "http://mozilla.org", LoadUrlFlags.external(), headers) - ) + + middleware.assertFirstAction(EngineAction.LoadUrlAction::class) { action -> + assertEquals(sessionCaptor.value.id, action.sessionId) + assertEquals("http://mozilla.org", action.url) + assertEquals(LoadUrlFlags.external(), action.flags) + assertEquals(headers, action.additionalHeaders) + } + verify(intent).putExtra(eq(EXTRA_SESSION_ID), any()) val customTabSession = sessionManager.all[0] @@ -113,13 +129,15 @@ class CustomTabIntentProcessorTest { @Test fun processPrivateCustomTabIntentWithDefaultHandlers() { - val store: BrowserStore = mock() + val middleware = CaptureActionsMiddleware() + val store = BrowserStore(middleware = listOf(middleware)) val engine: Engine = mock() - val sessionManager = spy(SessionManager(engine)) + val sessionManager = spy(SessionManager(engine, store)) val useCases = SessionUseCases(store, sessionManager) + val customTabsUseCases = CustomTabsUseCases(sessionManager, useCases.loadUrl) val handler = - CustomTabIntentProcessor(sessionManager, useCases.loadUrl, testContext.resources, true) + CustomTabIntentProcessor(customTabsUseCases.add, testContext.resources, true) val intent = mock() whenever(intent.action).thenReturn(Intent.ACTION_VIEW) @@ -131,9 +149,13 @@ class CustomTabIntentProcessorTest { val sessionCaptor = argumentCaptor() verify(sessionManager).add(sessionCaptor.capture(), eq(false), eq(null), eq(null), eq(null)) - verify(store).dispatch(EngineAction.LoadUrlAction( - sessionCaptor.value.id, "http://mozilla.org", LoadUrlFlags.external()) - ) + + middleware.assertFirstAction(EngineAction.LoadUrlAction::class) { action -> + assertEquals(sessionCaptor.value.id, action.sessionId) + assertEquals("http://mozilla.org", action.url) + assertEquals(LoadUrlFlags.external(), action.flags) + } + verify(intent).putExtra(eq(EXTRA_SESSION_ID), any()) val customTabSession = sessionManager.all[0] diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt index dc537292a89..3e09769b049 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/CustomTabsToolbarFeatureTest.kt @@ -13,30 +13,39 @@ import android.widget.FrameLayout import android.widget.ImageButton import androidx.core.view.forEach import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.action.ContentAction +import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.CustomTabActionButtonConfig import mozilla.components.browser.state.state.CustomTabConfig import mozilla.components.browser.state.state.CustomTabMenuItem +import mozilla.components.browser.state.state.createCustomTab +import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.concept.toolbar.Toolbar +import mozilla.components.feature.session.SessionUseCases +import mozilla.components.feature.tabs.CustomTabsUseCases import mozilla.components.support.test.any import mozilla.components.support.test.argumentCaptor +import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyList import org.mockito.Mockito.`when` import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doNothing import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times @@ -44,224 +53,241 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class CustomTabsToolbarFeatureTest { - @Test fun `start without sessionId invokes nothing`() { - val sessionManager: SessionManager = spy(SessionManager(mock())) - val session: Session = mock() - - val feature = spy(CustomTabsToolbarFeature(sessionManager, mock()) {}) + val store = BrowserStore() + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar: BrowserToolbar = mock() + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = null, useCases = useCases) {}) feature.start() - verify(sessionManager, never()).findSessionById(anyString()) - verify(feature, never()).initialize(session) + verify(feature, never()).init(any()) } @Test fun `start calls initialize with the sessionId`() { - val sessionManager: SessionManager = mock() - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, "") {}) + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla") - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) - `when`(session.customTabConfig).thenReturn(mock()) - doNothing().`when`(feature).addCloseButton(session, null) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = BrowserToolbar(testContext) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) feature.start() - verify(feature).initialize(session) + verify(feature).init(tab) // Calling start again should NOT call init again feature.start() - verify(feature, times(1)).initialize(session) - } - - @Test - @Suppress("DEPRECATION") - // TODO migrate to browser-state: https://github.com/mozilla-mobile/android-components/issues/4257 - fun `stop calls unregister`() { - val sessionManager: SessionManager = mock() - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) - - val feature = CustomTabsToolbarFeature(sessionManager, toolbar, "") {} - feature.stop() - - verify(session).unregister(any()) - } - - @Test - fun `initialize returns true if session is a customtab`() { - val session: Session = mock() - val toolbar = spy(BrowserToolbar(testContext)) - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - - var initialized = feature.initialize(session) - - assertFalse(initialized) - - `when`(session.customTabConfig).thenReturn(mock()) - - initialized = feature.initialize(session) - - assertTrue(initialized) + verify(feature, times(1)).init(tab) } @Test fun `initialize updates toolbar`() { - val session: Session = mock() - val toolbar = spy(BrowserToolbar(testContext)) - `when`(session.customTabConfig).thenReturn(mock()) + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla") - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = BrowserToolbar(testContext) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {} - feature.initialize(session) + feature.init(tab) assertFalse(toolbar.display.onUrlClicked.invoke()) } @Test - fun `initialize calls updateToolbarColor`() { - val sessionManager: SessionManager = mock() - val session: Session = mock() + fun `initialize updates toolbar, window and text color`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + toolbarColor = Color.RED, + navigationBarColor = Color.BLUE + )) + + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - `when`(session.customTabConfig).thenReturn(mock()) - - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, "") {}) - - feature.initialize(session) - - verify(feature).updateToolbarColor(anyInt(), anyInt()) - } - - @Test - fun `updateToolbarColor changes background and textColor`() { - val session: Session = mock() - val toolbar = spy(BrowserToolbar(testContext)) - `when`(session.customTabConfig).thenReturn(mock()) - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - - val colors = toolbar.display.colors - - feature.updateToolbarColor(null, null) - - assertEquals(colors, toolbar.display.colors) - assertNotEquals(Color.WHITE, toolbar.display.colors.title) - assertNotEquals(Color.WHITE, toolbar.display.colors.text) - - feature.updateToolbarColor(123, 456) - - assertEquals(Color.WHITE, toolbar.display.colors.title) - assertEquals(Color.WHITE, toolbar.display.colors.text) - } - - @Test - fun `updateToolbarColor changes status bar color`() { - val session: Session = mock() - val toolbar: BrowserToolbar = BrowserToolbar(testContext) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) val window: Window = mock() - `when`(session.customTabConfig).thenReturn(mock()) `when`(window.decorView).thenReturn(mock()) + val feature = CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases, window = window) {} - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", window = window) {}) + feature.init(tab) - feature.updateToolbarColor(null, null) + verify(toolbar).setBackgroundColor(Color.RED) + verify(window).statusBarColor = Color.RED + verify(window).navigationBarColor = Color.BLUE - verify(window, never()).statusBarColor = anyInt() - verify(window, never()).navigationBarColor = anyInt() - - feature.updateToolbarColor(123, 456) - - verify(window).statusBarColor = 123 - verify(window).navigationBarColor = 456 + assertEquals(Color.WHITE, toolbar.display.colors.title) + assertEquals(Color.WHITE, toolbar.display.colors.text) } @Test - fun `initialize calls addCloseButton`() { - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - `when`(session.customTabConfig).thenReturn(CustomTabConfig()) + fun `adds close button`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig()) - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {} - feature.initialize(session) + feature.start() - verify(feature).addCloseButton(session, null) + verify(toolbar).addNavigationAction(any()) } @Test - fun `initialize doesn't call addCloseButton if the button should be hidden`() { - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - `when`(session.customTabConfig).thenReturn(CustomTabConfig(showCloseButton = false)) - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) + fun `doesn't add close button if the button should be hidden`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + showCloseButton = false + )) + + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {} - feature.initialize(session) + feature.start() - verify(feature, never()).addCloseButton(session, null) + verify(toolbar, never()).addNavigationAction(any()) } @Test fun `close button invokes callback and removes session`() { - val session: Session = mock() - val sessionManager: SessionManager = mock() - val toolbar = BrowserToolbar(testContext) + val store = BrowserStore() + val sessionManager = spy(SessionManager(engine = mock(), store = store)) + sessionManager.add( + Session("https://www.mozilla.org", id = "mozilla").apply { + customTabConfig = CustomTabConfig() + }) + + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) var closeClicked = false - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, "") { closeClicked = true }) + val feature = CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) { + closeClicked = true + } - `when`(session.customTabConfig).thenReturn(CustomTabConfig()) + feature.start() - feature.initialize(session) + verify(toolbar).addNavigationAction(any()) val button = extractActionView(toolbar, testContext.getString(R.string.mozac_feature_customtabs_exit_button)) + + verify(sessionManager, never()).remove(any(), anyBoolean()) + button?.performClick() assertTrue(closeClicked) - verify(sessionManager).remove(session) + verify(sessionManager).remove(any(), anyBoolean()) } @Test - fun `initialize calls addShareButton`() { - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val config: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(config) - `when`(session.url).thenReturn("https://mozilla.org") - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) + fun `does not add share button by default`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig()) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) - feature.initialize(session) + feature.start() - verify(feature, never()).addShareButton(session) + verify(feature, never()).addShareButton(tab) + verify(toolbar, never()).addBrowserAction(any()) + } - // Show share menu only if config.showShareMenuItem has true - `when`(config.showShareMenuItem).thenReturn(true) + @Test + fun `adds share button`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + showShareMenuItem = true + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) - feature.initialize(session) + feature.start() - verify(feature).addShareButton(session) + verify(feature).addShareButton(tab) + verify(toolbar).addBrowserAction(any()) } @Test fun `share button uses custom share listener`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + showShareMenuItem = true + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val captor = argumentCaptor() + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) var clicked = false - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", shareListener = { clicked = true }) {}) - - `when`(session.customTabConfig).thenReturn(mock()) + val feature = CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + shareListener = { clicked = true } + ) {} - feature.addShareButton(session) + feature.start() + val captor = argumentCaptor() verify(toolbar).addBrowserAction(captor.capture()) val button = captor.value.createView(FrameLayout(testContext)) @@ -271,77 +297,49 @@ class CustomTabsToolbarFeatureTest { @Test fun `initialize calls addActionButton`() { - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - `when`(session.customTabConfig).thenReturn(mock()) - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - - feature.initialize(session) - - verify(feature).addActionButton(any(), any()) - } - - @Test - fun `add action button is invoked`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig()) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val captor = argumentCaptor() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - val customTabConfig: CustomTabConfig = mock() - val actionConfig: CustomTabActionButtonConfig = mock() - val size = 24 - val closeButtonIcon = Bitmap.createBitmap(IntArray(size * size), size, size, Bitmap.Config.ARGB_8888) - val pendingIntent: PendingIntent = mock() + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(session.url).thenReturn("https://example.com") - - feature.addActionButton(session, null) - - verify(toolbar, never()).addBrowserAction(any()) - - // Show action button only when CustomTabActionButtonConfig is not null - `when`(customTabConfig.actionButtonConfig).thenReturn(actionConfig) - `when`(actionConfig.description).thenReturn("test desc") - `when`(actionConfig.pendingIntent).thenReturn(pendingIntent) - `when`(actionConfig.icon).thenReturn(closeButtonIcon) - - feature.addActionButton(session, actionConfig) - - verify(toolbar).addBrowserAction(captor.capture()) + feature.start() - val button = captor.value.createView(FrameLayout(testContext)) - button.performClick() - verify(pendingIntent).send(any(), anyInt(), any()) + verify(feature).addActionButton(any(), any()) } @Test fun `action button is scaled to 24 width and 24 height`() { - val session: Session = mock() - val toolbar = spy(BrowserToolbar(testContext)) val captor = argumentCaptor() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - val customTabConfig: CustomTabConfig = mock() - val actionConfig: CustomTabActionButtonConfig = mock() - val size = 48 // this should be different than 24 to see the scaling is performed - val actionButtonIcon = Bitmap.createBitmap(IntArray(size * size), size, size, Bitmap.Config.ARGB_8888) + val size = 48 val pendingIntent: PendingIntent = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + actionButtonConfig = CustomTabActionButtonConfig( + description = "Button", + icon = Bitmap.createBitmap(IntArray(size * size), size, size, Bitmap.Config.ARGB_8888), + pendingIntent = pendingIntent + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(session.url).thenReturn("https://example.com") - - feature.addActionButton(session, null) - - verify(toolbar, never()).addBrowserAction(any()) - - `when`(customTabConfig.actionButtonConfig).thenReturn(actionConfig) - `when`(actionConfig.description).thenReturn("test desc") - `when`(actionConfig.pendingIntent).thenReturn(pendingIntent) - `when`(actionConfig.icon).thenReturn(actionButtonIcon) - - feature.addActionButton(session, actionConfig) + feature.start() + verify(feature).addActionButton(any(), any()) verify(toolbar).addBrowserAction(captor.capture()) val button = captor.value.createView(FrameLayout(testContext)) @@ -351,88 +349,84 @@ class CustomTabsToolbarFeatureTest { @Test fun `initialize calls addMenuItems when config has items`() { - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - - feature.initialize(session) - - verify(feature, never()).addMenuItems(any(), anyList(), anyInt()) - - `when`(customTabConfig.menuItems).thenReturn(listOf(CustomTabMenuItem("Share", mock()))) + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy(CustomTabsToolbarFeature(store, toolbar, sessionId = "mozilla", useCases = useCases) {}) - feature.initialize(session) + feature.start() verify(feature).addMenuItems(any(), anyList(), anyInt()) } @Test fun `initialize calls addMenuItems when menuBuilder has items`() { - val session: Session = mock() - val menuBuilder: BrowserMenuBuilder = mock() - val toolbar = BrowserToolbar(testContext) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - `when`(menuBuilder.items).thenReturn(listOf(mock(), mock())) - - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", menuBuilder) {}) + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())) + ) {} + ) - feature.initialize(session) + feature.start() verify(feature).addMenuItems(any(), anyList(), anyInt()) } - @Test - fun `initialize never calls addMenuItems when no config or builder items available`() { - val session: Session = mock() - val menuBuilder: BrowserMenuBuilder = mock() - val toolbar = BrowserToolbar(testContext) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - - // With NO config or builder. - var feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - - feature.initialize(session) - - verify(feature, never()).addMenuItems(any(), anyList(), anyInt()) - - // With only builder but NO items. - feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", menuBuilder) {}) - - feature.initialize(session) - - // With only builder and items. - `when`(menuBuilder.items).thenReturn(listOf()) - - feature.initialize(session) - - verify(feature, never()).addMenuItems(any(), anyList(), anyInt()) - - // With config with NO items and builder with items. - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - - feature.initialize(session) - - verify(feature, never()).addMenuItems(any(), anyList(), anyInt()) - } - @Test fun `menu items added WITHOUT current items`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "") {}) - val customTabConfig: CustomTabConfig = mock() - - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases + ) {} + ) - feature.addMenuItems(session, listOf(CustomTabMenuItem("Share", mock())), 0) + feature.start() val menuBuilder = toolbar.display.menuBuilder assertEquals(1, menuBuilder!!.items.size) @@ -440,33 +434,64 @@ class CustomTabsToolbarFeatureTest { @Test fun `menu items added WITH current items`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val builder: BrowserMenuBuilder = mock() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", builder) {}) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - `when`(builder.items).thenReturn(listOf(mock(), mock())) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())) + ) {} + ) - feature.addMenuItems(session, listOf(CustomTabMenuItem("Share", mock())), 0) + feature.start() - val menuBuilder = toolbar.display.menuBuilder!! - assertEquals(3, menuBuilder.items.size) + val menuBuilder = toolbar.display.menuBuilder + assertEquals(3, menuBuilder!!.items.size) } @Test fun `menu item added at specified index`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val builder: BrowserMenuBuilder = mock() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", builder) {}) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - `when`(builder.items).thenReturn(listOf(mock(), mock())) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 1 + ) {} + ) - feature.addMenuItems(session, listOf(CustomTabMenuItem("Share", mock())), 1) + feature.start() val menuBuilder = toolbar.display.menuBuilder!! @@ -476,16 +501,32 @@ class CustomTabsToolbarFeatureTest { @Test fun `menu item added appended if index too large`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val builder: BrowserMenuBuilder = mock() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", builder) {}) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - `when`(builder.items).thenReturn(listOf(mock(), mock())) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) {} + ) - feature.addMenuItems(session, listOf(CustomTabMenuItem("Share", mock())), 4) + feature.start() val menuBuilder = toolbar.display.menuBuilder!! @@ -495,16 +536,32 @@ class CustomTabsToolbarFeatureTest { @Test fun `menu item added appended if index too small`() { - val session: Session = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + menuItems = listOf( + CustomTabMenuItem("Share", mock()) + ) + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) val toolbar = spy(BrowserToolbar(testContext)) - val builder: BrowserMenuBuilder = mock() - val feature = spy(CustomTabsToolbarFeature(mock(), toolbar, "", builder) {}) - val customTabConfig: CustomTabConfig = mock() - `when`(session.customTabConfig).thenReturn(customTabConfig) - `when`(customTabConfig.menuItems).thenReturn(emptyList()) - `when`(builder.items).thenReturn(listOf(mock(), mock())) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = -4 + ) {} + ) - feature.addMenuItems(session, listOf(CustomTabMenuItem("Share", mock())), -4) + feature.start() val menuBuilder = toolbar.display.menuBuilder!! @@ -514,16 +571,31 @@ class CustomTabsToolbarFeatureTest { @Test fun `onBackPressed removes initialized session`() { - val sessionId = "123" - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val sessionManager: SessionManager = mock() + val store = BrowserStore() + val sessionManager = SessionManager(engine = mock(), store = store) + sessionManager.add(Session("https://www.mozilla.org", id = "mozilla").apply { + customTabConfig = CustomTabConfig() + }) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) var closeExecuted = false - `when`(session.customTabConfig).thenReturn(mock()) - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) { + closeExecuted = true + } + ) - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, sessionId) { closeExecuted = true }) - feature.initialized = true + feature.start() val result = feature.onBackPressed() @@ -533,15 +605,31 @@ class CustomTabsToolbarFeatureTest { @Test fun `onBackPressed without a session does nothing`() { - val sessionId = null - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val sessionManager: SessionManager = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig()) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) var closeExecuted = false - `when`(session.customTabConfig).thenReturn(mock()) - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = null, + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) { + closeExecuted = true + } + ) - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, sessionId) { closeExecuted = true }) + feature.start() val result = feature.onBackPressed() @@ -551,17 +639,29 @@ class CustomTabsToolbarFeatureTest { @Test fun `onBackPressed with uninitialized feature returns false`() { - val sessionId = "123" - val session: Session = mock() - val toolbar = BrowserToolbar(testContext) - val sessionManager: SessionManager = mock() + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig()) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) var closeExecuted = false - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) - - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, sessionId) { - closeExecuted = true - }) - feature.initialized = false + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = null, + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) { + closeExecuted = true + } + ) val result = feature.onBackPressed() @@ -570,89 +670,198 @@ class CustomTabsToolbarFeatureTest { } @Test - fun `keep readableColor if toolbarColor is provided`() { - val sessionManager: SessionManager = mock() - val toolbar = BrowserToolbar(testContext) - val session: Session = mock() - val customTabConfig: CustomTabConfig = mock() - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, "") {}) - - assertEquals(Color.WHITE, feature.readableColor) - - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) - `when`(session.customTabConfig).thenReturn(customTabConfig) + fun `readableColor - White on Black`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + toolbarColor = Color.BLACK + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) {} + ) - feature.initialize(session) + feature.start() assertEquals(Color.WHITE, feature.readableColor) + assertEquals(Color.WHITE, toolbar.display.colors.text) + } - `when`(customTabConfig.toolbarColor).thenReturn(Color.WHITE) + @Test + fun `readableColor - Black on White`() { + val tab = createCustomTab("https://www.mozilla.org", id = "mozilla", config = CustomTabConfig( + toolbarColor = Color.WHITE + )) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) {} + ) - feature.initialize(session) + feature.start() assertEquals(Color.BLACK, feature.readableColor) + assertEquals(Color.BLACK, toolbar.display.colors.text) } @Test - @Suppress("DEPRECATION") - // TODO migrate to browser-state: https://github.com/mozilla-mobile/android-components/issues/4257 fun `show title only if not empty`() { - val sessionManager: SessionManager = mock() - val toolbar = BrowserToolbar(testContext) - val session = spy(Session("https://mozilla.org")) - val customTabConfig: CustomTabConfig = mock() - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, "") {}) - val title = "Internet for people, not profit - Mozilla" - - `when`(sessionManager.findSessionById(anyString())).thenReturn(session) - `when`(session.customTabConfig).thenReturn(customTabConfig) + val dispatcher = TestCoroutineDispatcher() + Dispatchers.setMain(dispatcher) + + val tab = createCustomTab( + "https://www.mozilla.org", + id = "mozilla", + config = CustomTabConfig(), + title = "" + ) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) {} + ) feature.start() - session.notifyObservers { onTitleChanged(session, "") } - assertEquals("", toolbar.title) - session.notifyObservers { onTitleChanged(session, title) } + store.dispatch( + ContentAction.UpdateTitleAction( + "mozilla", + "Internet for people, not profit - Mozilla" + ) + ).joinBlocking() + + dispatcher.advanceUntilIdle() + + assertEquals("Internet for people, not profit - Mozilla", toolbar.title) - assertEquals(title, toolbar.title) + Dispatchers.resetMain() } @Test fun `Will use URL as title if title was shown once and is now empty`() { - val sessionManager = SessionManager(mock()) - val toolbar = BrowserToolbar(testContext) - val session = Session("https://mozilla.org").also { - it.customTabConfig = mock() - sessionManager.add(it) - } - val feature = spy(CustomTabsToolbarFeature(sessionManager, toolbar, session.id) {}) + val dispatcher = TestCoroutineDispatcher() + Dispatchers.setMain(dispatcher) + + val tab = createCustomTab( + "https://www.mozilla.org", + id = "mozilla", + config = CustomTabConfig(), + title = "" + ) + val store = BrowserStore(BrowserState( + customTabs = listOf(tab) + )) + val sessionManager = SessionManager(engine = mock(), store = store) + val toolbar = spy(BrowserToolbar(testContext)) + val useCases = CustomTabsUseCases( + sessionManager = sessionManager, + loadUrlUseCase = SessionUseCases(store, sessionManager).loadUrl + ) + val feature = spy( + CustomTabsToolbarFeature( + store, + toolbar, + sessionId = "mozilla", + useCases = useCases, + menuBuilder = BrowserMenuBuilder(listOf(mock(), mock())), + menuItemIndex = 4 + ) {} + ) + + feature.start() feature.start() assertEquals("", toolbar.title) - session.url = "https://www.mozilla.org/en-US/firefox/" + store.dispatch( + ContentAction.UpdateUrlAction("mozilla", "https://www.mozilla.org/en-US/firefox/") + ).joinBlocking() + + dispatcher.advanceUntilIdle() assertEquals("", toolbar.title) - session.title = "Firefox - Protect your life online with privacy-first products" + store.dispatch( + ContentAction.UpdateTitleAction("mozilla", "Firefox - Protect your life online with privacy-first products") + ).joinBlocking() + + dispatcher.advanceUntilIdle() assertEquals("Firefox - Protect your life online with privacy-first products", toolbar.title) - session.url = "https://github.com/mozilla-mobile/android-components" + store.dispatch( + ContentAction.UpdateUrlAction("mozilla", "https://github.com/mozilla-mobile/android-components") + ).joinBlocking() + + dispatcher.advanceUntilIdle() assertEquals("Firefox - Protect your life online with privacy-first products", toolbar.title) - session.title = "" + store.dispatch( + ContentAction.UpdateTitleAction("mozilla", "") + ).joinBlocking() + + dispatcher.advanceUntilIdle() assertEquals("https://github.com/mozilla-mobile/android-components", toolbar.title) - session.title = "A collection of Android libraries to build browsers or browser-like applications." + store.dispatch( + ContentAction.UpdateTitleAction("mozilla", "A collection of Android libraries to build browsers or browser-like applications.") + ).joinBlocking() + + dispatcher.advanceUntilIdle() + + assertEquals("A collection of Android libraries to build browsers or browser-like applications.", toolbar.title) - assertEquals("A collection of Android libraries to build browsers or browser-like applications.", session.title) + store.dispatch( + ContentAction.UpdateTitleAction("mozilla", "") + ).joinBlocking() - session.title = "" + dispatcher.advanceUntilIdle() assertEquals("https://github.com/mozilla-mobile/android-components", toolbar.title) } diff --git a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt index 26fb8992b62..54ab414a3c5 100644 --- a/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt +++ b/components/feature/customtabs/src/test/java/mozilla/components/feature/customtabs/feature/CustomTabSessionTitleObserverTest.kt @@ -5,7 +5,8 @@ package mozilla.components.feature.customtabs.feature import androidx.test.ext.junit.runners.AndroidJUnit4 -import mozilla.components.browser.session.Session +import mozilla.components.browser.state.state.CustomTabSessionState +import mozilla.components.browser.state.state.createCustomTab import mozilla.components.concept.toolbar.AutocompleteDelegate import mozilla.components.concept.toolbar.Toolbar import mozilla.components.support.test.ThrowProperty @@ -13,7 +14,6 @@ import mozilla.components.support.test.mock import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -23,43 +23,48 @@ class CustomTabSessionTitleObserverTest { @Test fun `show title only if not empty`() { val toolbar: Toolbar = mock() - val session = Mockito.spy(Session("https://mozilla.org")) val observer = CustomTabSessionTitleObserver(toolbar) + val url = "https://www.mozilla.org" val title = "Internet for people, not profit - Mozilla" - observer.onTitleChanged(session, title = "") + observer.onTab(createCustomTab(url, title = "")) verify(toolbar, never()).title = "" - observer.onTitleChanged(session, title = title) + observer.onTab(createCustomTab(url, title = title)) verify(toolbar).title = title } @Test - @Suppress("DEPRECATION") - // TODO migrate to browser-state: https://github.com/mozilla-mobile/android-components/issues/4257 fun `Will use URL as title if title was shown once and is now empty`() { val toolbar = MockToolbar() - val session = Session("https://mozilla.org") - session.register(CustomTabSessionTitleObserver(toolbar)) + var tab = createCustomTab("https://mozilla.org") + val observer = CustomTabSessionTitleObserver(toolbar) + observer.onTab(tab) assertEquals("", toolbar.title) - session.url = "https://www.mozilla.org/en-US/firefox/" + tab = tab.withUrl("https://www.mozilla.org/en-US/firefox/") + observer.onTab(tab) assertEquals("", toolbar.title) - session.title = "Firefox - Protect your life online with privacy-first products" + tab = tab.withTitle("Firefox - Protect your life online with privacy-first products") + observer.onTab(tab) assertEquals("Firefox - Protect your life online with privacy-first products", toolbar.title) - session.url = "https://github.com/mozilla-mobile/android-components" + tab = tab.withUrl("https://github.com/mozilla-mobile/android-components") + observer.onTab(tab) assertEquals("Firefox - Protect your life online with privacy-first products", toolbar.title) - session.title = "" + tab = tab.withTitle("") + observer.onTab(tab) assertEquals("https://github.com/mozilla-mobile/android-components", toolbar.title) - session.title = "A collection of Android libraries to build browsers or browser-like applications." + tab = tab.withTitle("A collection of Android libraries to build browsers or browser-like applications.") + observer.onTab(tab) assertEquals("A collection of Android libraries to build browsers or browser-like applications.", toolbar.title) - session.title = "" + tab = tab.withTitle("") + observer.onTab(tab) assertEquals("https://github.com/mozilla-mobile/android-components", toolbar.title) } @@ -88,3 +93,11 @@ class CustomTabSessionTitleObserverTest { override fun editMode() = Unit } } + +private fun CustomTabSessionState.withTitle(title: String) = copy( + content = content.copy(title = title) +) + +private fun CustomTabSessionState.withUrl(url: String) = copy( + content = content.copy(url = url) +) diff --git a/components/feature/tabs/src/main/java/mozilla/components/feature/tabs/CustomTabsUseCases.kt b/components/feature/tabs/src/main/java/mozilla/components/feature/tabs/CustomTabsUseCases.kt new file mode 100644 index 00000000000..3984e049196 --- /dev/null +++ b/components/feature/tabs/src/main/java/mozilla/components/feature/tabs/CustomTabsUseCases.kt @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.feature.tabs + +import mozilla.components.browser.session.Session +import mozilla.components.browser.session.SessionManager +import mozilla.components.browser.state.state.CustomTabConfig +import mozilla.components.browser.state.state.SessionState +import mozilla.components.concept.engine.EngineSession +import mozilla.components.feature.session.SessionUseCases + +/** + * UseCases for custom tabs. + */ +class CustomTabsUseCases( + sessionManager: SessionManager, + loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase +) { + /** + * Use case for adding a new custom tab. + */ + class AddCustomTabUseCase( + private val sessionManager: SessionManager, + private val loadUrlUseCase: SessionUseCases.DefaultLoadUrlUseCase + ) { + /** + * Adds a new custom tab with URL [url]. + */ + operator fun invoke( + url: String, + customTabConfig: CustomTabConfig, + private: Boolean, + additionalHeaders: Map? = null + ): String { + val session = Session(url, private = private, source = SessionState.Source.CUSTOM_TAB) + session.customTabConfig = customTabConfig + + sessionManager.add(session) + + loadUrlUseCase(url, session.id, EngineSession.LoadUrlFlags.external(), additionalHeaders) + + return session.id + } + } + + /** + * Use case for removing a custom tab. + */ + class RemoveCustomTabUseCase( + private val sessionManager: SessionManager + ) { + /** + * Removes the custom tab with the given [customTabId]. + */ + operator fun invoke(customTabId: String): Boolean { + val session = sessionManager.findSessionById(customTabId) + if (session != null) { + sessionManager.remove(session) + return true + } + return false + } + } + + val add by lazy { AddCustomTabUseCase(sessionManager, loadUrlUseCase) } + val remove by lazy { RemoveCustomTabUseCase(sessionManager) } +} diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt index 684644b0c8a..4eeaf16df54 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/DefaultComponents.kt @@ -67,6 +67,7 @@ import mozilla.components.feature.search.region.RegionMiddleware import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.sitepermissions.SitePermissionsStorage +import mozilla.components.feature.tabs.CustomTabsUseCases import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.webnotifications.WebNotificationFeature import mozilla.components.lib.crash.Crash @@ -169,6 +170,8 @@ open class DefaultComponents(private val applicationContext: Context) { val sessionUseCases by lazy { SessionUseCases(store, sessionManager) } + val customTabsUseCases by lazy { CustomTabsUseCases(sessionManager, sessionUseCases.loadUrl) } + // Addons val addonManager by lazy { AddonManager(store, engine, addonCollectionProvider, addonUpdater) @@ -246,7 +249,7 @@ open class DefaultComponents(private val applicationContext: Context) { relationChecker, customTabsStore ), - CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, applicationContext.resources) + CustomTabIntentProcessor(customTabsUseCases.add, applicationContext.resources) ) } diff --git a/samples/browser/src/main/java/org/mozilla/samples/browser/ExternalAppBrowserFragment.kt b/samples/browser/src/main/java/org/mozilla/samples/browser/ExternalAppBrowserFragment.kt index 8afee340a6a..484450ce0b2 100644 --- a/samples/browser/src/main/java/org/mozilla/samples/browser/ExternalAppBrowserFragment.kt +++ b/samples/browser/src/main/java/org/mozilla/samples/browser/ExternalAppBrowserFragment.kt @@ -41,9 +41,10 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler customTabsToolbarFeature.set( feature = CustomTabsToolbarFeature( - components.sessionManager, + components.store, layout.toolbar, sessionId, + components.customTabsUseCases, components.menuBuilder, window = activity?.window, closeListener = { activity?.finish() }),