From 83ceec62897580def66a74a0d080619eb54f384f Mon Sep 17 00:00:00 2001 From: Yeon Taek Jeong Date: Thu, 11 Jul 2019 10:24:58 -0700 Subject: [PATCH] For #201: Search Widget --- app/src/main/AndroidManifest.xml | 10 ++ .../java/org/mozilla/fenix/HomeActivity.kt | 13 ++ .../mozilla/fenix/IntentReceiverActivity.kt | 61 +++++++- .../org/mozilla/fenix/SearchWidgetProvider.kt | 136 ++++++++++++++++++ .../mozilla/fenix/search/SearchFragment.kt | 9 +- .../mozilla/fenix/search/SearchInteractor.kt | 16 +++ .../res/drawable-hdpi/fenix_search_widget.png | Bin 0 -> 38156 bytes app/src/main/res/drawable/ic_logo_widget.xml | 26 ++++ .../res/drawable/ic_microphone_widget.xml | 8 ++ .../res/drawable/rounded_white_corners.xml | 9 ++ .../res/layout/search_widget_extra_large.xml | 45 ++++++ .../res/layout/search_widget_extra_small.xml | 25 ++++ .../main/res/layout/search_widget_large.xml | 45 ++++++ .../main/res/layout/search_widget_medium.xml | 45 ++++++ .../main/res/layout/search_widget_small.xml | 37 +++++ app/src/main/res/navigation/nav_graph.xml | 11 ++ app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/search_widget_info.xml | 15 ++ .../fenix/search/SearchInteractorTest.kt | 26 +++- 20 files changed, 538 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/SearchWidgetProvider.kt create mode 100644 app/src/main/res/drawable-hdpi/fenix_search_widget.png create mode 100644 app/src/main/res/drawable/ic_logo_widget.xml create mode 100644 app/src/main/res/drawable/ic_microphone_widget.xml create mode 100644 app/src/main/res/drawable/rounded_white_corners.xml create mode 100644 app/src/main/res/layout/search_widget_extra_large.xml create mode 100644 app/src/main/res/layout/search_widget_extra_small.xml create mode 100644 app/src/main/res/layout/search_widget_large.xml create mode 100644 app/src/main/res/layout/search_widget_medium.xml create mode 100644 app/src/main/res/layout/search_widget_small.xml create mode 100644 app/src/main/res/xml/search_widget_info.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0d7a2b4a..2976ff0c8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,6 +108,16 @@ + + + + + + + diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index bc951f124..ec5cfb62a 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -189,6 +189,17 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback } private fun handleOpenedFromExternalSourceIfNecessary(intent: Intent?) { + if (intent?.extras?.getBoolean(OPEN_TO_BROWSER_AND_LOAD) == true) { + this.intent.putExtra(OPEN_TO_BROWSER_AND_LOAD, false) + openToBrowserAndLoad(intent.getStringExtra( + IntentReceiverActivity.SPEECH_PROCESSING), true, BrowserDirection.FromGlobal, forceSearch = true) + return + } else if (intent?.extras?.getBoolean(OPEN_TO_SEARCH) == true) { + this.intent.putExtra(OPEN_TO_SEARCH, false) + navHost.navController.nav(null, NavGraphDirections.actionGlobalSearch(null, true)) + return + } + if (intent?.extras?.getBoolean(OPEN_TO_BROWSER) != true) return this.intent.putExtra(OPEN_TO_BROWSER, false) @@ -391,6 +402,8 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback companion object { const val OPEN_TO_BROWSER = "open_to_browser" + const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load" + const val OPEN_TO_SEARCH = "open_to_search" } } diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt index 395532df8..e034cd12d 100644 --- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix import android.app.Activity import android.content.Intent import android.os.Bundle +import android.speech.RecognizerIntent import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import org.mozilla.fenix.components.NotificationManager @@ -17,10 +18,19 @@ import org.mozilla.fenix.utils.Settings class IntentReceiverActivity : Activity() { + // Holds the intent that initially started this activity + // so that it can persist through the speech activity. + private var previousIntent: Intent? = null + @Suppress("ComplexMethod") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + previousIntent = savedInstanceState?.get(PREVIOUS_INTENT) as Intent? + if (previousIntent?.getBooleanExtra(SPEECH_PROCESSING, false) == true) { + return + } + val isPrivate = Settings.getInstance(this).usePrivateMode MainScope().launch { @@ -34,12 +44,17 @@ class IntentReceiverActivity : Activity() { if (isPrivate) components.utils.privateIntentProcessor else components.utils.intentProcessor ) - intentProcessors.any { it.process(intent) } - setIntentActivity(intent) + if (intent.getBooleanExtra(SPEECH_PROCESSING, false)) { + previousIntent = intent + displaySpeechRecognizer() + } else { + intentProcessors.any { it.process(intent) } + setIntentActivity(intent) - startActivity(intent) + startActivity(intent) - finish() + finish() + } } } @@ -78,4 +93,42 @@ class IntentReceiverActivity : Activity() { intent.putExtra(HomeActivity.OPEN_TO_BROWSER, openToBrowser) } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(PREVIOUS_INTENT, previousIntent) + } + + private fun displaySpeechRecognizer() { + val intentSpeech = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + } + + startActivityForResult(intentSpeech, SPEECH_REQUEST_CODE) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) { + val spokenText: String? = + data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.let { results -> + results[0] + } + + previousIntent?.let { + it.putExtra(SPEECH_PROCESSING, spokenText) + it.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true) + startActivity(it) + } + } + + finish() + } + + companion object { + private const val SPEECH_REQUEST_CODE = 0 + const val SPEECH_PROCESSING = "speech_processing" + const val PREVIOUS_INTENT = "previous_intent" + } } diff --git a/app/src/main/java/org/mozilla/fenix/SearchWidgetProvider.kt b/app/src/main/java/org/mozilla/fenix/SearchWidgetProvider.kt new file mode 100644 index 000000000..62c436e7e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/SearchWidgetProvider.kt @@ -0,0 +1,136 @@ +/* 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 org.mozilla.fenix + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.widget.RemoteViews + +class SearchWidgetProvider : AppWidgetProvider() { + + override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { + val textSearchIntent = createTextSearchIntent(context) + val voiceSearchIntent = createVoiceSearchIntent(context) + + appWidgetIds.forEach { appWidgetId -> + val currentWidth = appWidgetManager.getAppWidgetOptions(appWidgetId).getInt(OPTION_APPWIDGET_MIN_WIDTH) + val layoutSize = getLayoutSize(currentWidth) + val layout = getLayout(layoutSize) + val text = getText(layoutSize, context) + + val views = createRemoteViews(context, layout, textSearchIntent, voiceSearchIntent, text) + appWidgetManager.updateAppWidget(appWidgetId, views) + } + } + + override fun onAppWidgetOptionsChanged( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetId: Int, + newOptions: Bundle? + ) { + val textSearchIntent = createTextSearchIntent(context) + val voiceSearchIntent = createVoiceSearchIntent(context) + + val currentWidth = appWidgetManager.getAppWidgetOptions(appWidgetId).getInt(OPTION_APPWIDGET_MIN_WIDTH) + val layoutSize = getLayoutSize(currentWidth) + val layout = getLayout(layoutSize) + val text = getText(layoutSize, context) + + val views = createRemoteViews(context, layout, textSearchIntent, voiceSearchIntent, text) + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + private fun getLayoutSize(dp: Int) = when { + dp >= DP_EXTRA_LARGE -> SearchWidgetProviderSize.EXTRA_LARGE + dp >= DP_LARGE -> SearchWidgetProviderSize.LARGE + dp >= DP_MEDIUM -> SearchWidgetProviderSize.MEDIUM + dp >= DP_SMALL -> SearchWidgetProviderSize.SMALL + else -> SearchWidgetProviderSize.EXTRA_SMALL + } + + private fun getLayout(size: SearchWidgetProviderSize) = when (size) { + SearchWidgetProviderSize.EXTRA_LARGE -> R.layout.search_widget_extra_large + SearchWidgetProviderSize.LARGE -> R.layout.search_widget_large + SearchWidgetProviderSize.MEDIUM -> R.layout.search_widget_medium + SearchWidgetProviderSize.SMALL -> R.layout.search_widget_small + SearchWidgetProviderSize.EXTRA_SMALL -> R.layout.search_widget_extra_small + } + + private fun getText(layout: SearchWidgetProviderSize, context: Context) = when (layout) { + SearchWidgetProviderSize.MEDIUM -> context.getString(R.string.search_widget_text_short) + SearchWidgetProviderSize.LARGE, + SearchWidgetProviderSize.EXTRA_LARGE -> context.getString(R.string.search_widget_text_long) + else -> null + } + + private fun createTextSearchIntent(context: Context): PendingIntent { + return Intent(context, HomeActivity::class.java) + .let { intent -> + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + intent.putExtra(HomeActivity.OPEN_TO_SEARCH, true) + PendingIntent.getActivity(context, REQUEST_CODE_NEW_TAB, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + } + + private fun createVoiceSearchIntent(context: Context): PendingIntent { + return Intent(context, IntentReceiverActivity::class.java) + .let { intent -> + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + intent.putExtra(IntentReceiverActivity.SPEECH_PROCESSING, true) + PendingIntent.getActivity(context, REQUEST_CODE_VOICE, intent, 0) + } + } + + private fun createRemoteViews( + context: Context, + layout: Int, + textSearchIntent: PendingIntent, + voiceSearchIntent: PendingIntent, + text: String? + ): RemoteViews { + return RemoteViews(context.packageName, layout).apply { + when (layout) { + R.layout.search_widget_extra_small -> { + setOnClickPendingIntent(R.id.button_search_widget_new_tab, textSearchIntent) + } + R.layout.search_widget_small -> { + setOnClickPendingIntent(R.id.button_search_widget_new_tab, textSearchIntent) + setOnClickPendingIntent(R.id.button_search_widget_voice, voiceSearchIntent) + } + R.layout.search_widget_medium, + R.layout.search_widget_large, + R.layout.search_widget_extra_large -> { + setOnClickPendingIntent(R.id.button_search_widget_new_tab, textSearchIntent) + setOnClickPendingIntent(R.id.button_search_widget_voice, voiceSearchIntent) + setTextViewText(R.id.text_search_widget, text) + } + } + } + } + + // Cell sizes obtained from the actual dimensions listed in search widget specs + companion object { + private const val DP_SMALL = 100 + private const val DP_MEDIUM = 192 + private const val DP_LARGE = 256 + private const val DP_EXTRA_LARGE = 360 + private const val REQUEST_CODE_NEW_TAB = 0 + private const val REQUEST_CODE_VOICE = 1 + } +} + +enum class SearchWidgetProviderSize { + EXTRA_SMALL, + SMALL, + MEDIUM, + LARGE, + EXTRA_LARGE +} diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index e925464a2..c47709ef3 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -65,6 +65,11 @@ class SearchFragment : Fragment(), BackHandler { ?.let { it.sessionId } ?.let(requireComponents.core.sessionManager::findSessionById) + val displaySearchShortcuts = arguments + ?.let(SearchFragmentArgs.Companion::fromBundle) + ?.let { it.displaySearchShortcuts } + ?: false + val view = inflater.inflate(R.layout.fragment_search, container, false) val url = session?.url ?: "" @@ -72,7 +77,7 @@ class SearchFragment : Fragment(), BackHandler { SearchStore( SearchState( query = url, - showShortcutEnginePicker = false, + showShortcutEnginePicker = displaySearchShortcuts, searchEngineSource = SearchEngineSource.Default( requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()) ), @@ -165,6 +170,8 @@ class SearchFragment : Fragment(), BackHandler { } else { requireComponents.analytics.metrics.track(Event.SearchShortcutMenuOpened) } + + searchInteractor.turnOnStartedTyping() } consumeFrom(searchStore) { diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt b/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt index b3d749e31..c88f03413 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchInteractor.kt @@ -30,6 +30,10 @@ class SearchInteractor( private val store: SearchStore ) : AwesomeBarInteractor, ToolbarInteractor { + data class UserTypingCheck(var ranOnTextChanged: Boolean, var userHasTyped: Boolean) + + private val userTypingCheck = UserTypingCheck(false, !store.state.showShortcutEnginePicker) + override fun onUrlCommitted(url: String) { if (url.isNotBlank()) { (context as HomeActivity).openToBrowserAndLoad( @@ -55,6 +59,13 @@ class SearchInteractor( override fun onTextChanged(text: String) { store.dispatch(SearchAction.UpdateQuery(text)) + + if (userTypingCheck.ranOnTextChanged && !userTypingCheck.userHasTyped) { + store.dispatch(SearchAction.ShowSearchShortcutEnginePicker(false)) + turnOnStartedTyping() + } + + userTypingCheck.ranOnTextChanged = true } override fun onUrlTapped(url: String) { @@ -90,6 +101,11 @@ class SearchInteractor( navController.navigate(directions) } + fun turnOnStartedTyping() { + userTypingCheck.ranOnTextChanged = true + userTypingCheck.userHasTyped = true + } + override fun onExistingSessionSelected(session: Session) { val directions = SearchFragmentDirections.actionSearchFragmentToBrowserFragment(null) navController.nav(R.id.searchFragment, directions) diff --git a/app/src/main/res/drawable-hdpi/fenix_search_widget.png b/app/src/main/res/drawable-hdpi/fenix_search_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b9a00cbbb9bb5987ebe892f5ecbde943b332d5 GIT binary patch literal 38156 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�B?sA;o*VIj*@~?K$`|8J1IvQFi2PH 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 E0Kze_B>(^b literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_logo_widget.xml b/app/src/main/res/drawable/ic_logo_widget.xml new file mode 100644 index 000000000..4f07b2fdc --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_widget.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_microphone_widget.xml b/app/src/main/res/drawable/ic_microphone_widget.xml new file mode 100644 index 000000000..7cbf04262 --- /dev/null +++ b/app/src/main/res/drawable/ic_microphone_widget.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/rounded_white_corners.xml b/app/src/main/res/drawable/rounded_white_corners.xml new file mode 100644 index 000000000..88f681fd0 --- /dev/null +++ b/app/src/main/res/drawable/rounded_white_corners.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_widget_extra_large.xml b/app/src/main/res/layout/search_widget_extra_large.xml new file mode 100644 index 000000000..e501858c1 --- /dev/null +++ b/app/src/main/res/layout/search_widget_extra_large.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_widget_extra_small.xml b/app/src/main/res/layout/search_widget_extra_small.xml new file mode 100644 index 000000000..87d204637 --- /dev/null +++ b/app/src/main/res/layout/search_widget_extra_small.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_widget_large.xml b/app/src/main/res/layout/search_widget_large.xml new file mode 100644 index 000000000..1e08a2ea7 --- /dev/null +++ b/app/src/main/res/layout/search_widget_large.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_widget_medium.xml b/app/src/main/res/layout/search_widget_medium.xml new file mode 100644 index 000000000..350a3ab11 --- /dev/null +++ b/app/src/main/res/layout/search_widget_medium.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_widget_small.xml b/app/src/main/res/layout/search_widget_small.xml new file mode 100644 index 000000000..be7cdec96 --- /dev/null +++ b/app/src/main/res/layout/search_widget_small.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 6942e148f..a618ea96b 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -11,6 +11,12 @@ app:popUpTo="@id/nav_graph" app:popUpToInclusive="true" /> + + @@ -66,6 +72,11 @@ android:name="session_id" app:argType="string" app:nullable="true" /> + #E0E0E6 #C50042 #312A65 + + + #737373 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d9f644f55..a65035a7d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,6 +81,12 @@ Fill link from clipboard + + + Search + + Search the web + Settings diff --git a/app/src/main/res/xml/search_widget_info.xml b/app/src/main/res/xml/search_widget_info.xml new file mode 100644 index 000000000..c8d9ebbc0 --- /dev/null +++ b/app/src/main/res/xml/search_widget_info.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt index 0a2cc7733..2ef79363c 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt @@ -38,6 +38,7 @@ class SearchInteractorTest { every { context.openToBrowserAndLoad(any(), any(), any(), any(), any(), any()) } just Runs every { store.state } returns state + every { state.showShortcutEnginePicker } returns true every { state.session } returns null every { state.searchEngineSource } returns searchEngine @@ -58,7 +59,11 @@ class SearchInteractorTest { @Test fun onEditingCanceled() { val navController: NavController = mockk(relaxed = true) - val interactor = SearchInteractor(mockk(), navController, mockk()) + val store: SearchStore = mockk() + + every { store.state } returns mockk(relaxed = true) + + val interactor = SearchInteractor(mockk(), navController, store) interactor.onEditingCanceled() @@ -70,6 +75,9 @@ class SearchInteractorTest { @Test fun onTextChanged() { val store: SearchStore = mockk(relaxed = true) + + every { store.state } returns mockk(relaxed = true) + val interactor = SearchInteractor(mockk(), mockk(), store) interactor.onTextChanged("test") @@ -88,6 +96,7 @@ class SearchInteractorTest { every { store.state } returns state every { state.session } returns null + every { state.showShortcutEnginePicker } returns true val interactor = SearchInteractor(context, mockk(), store) @@ -117,6 +126,7 @@ class SearchInteractorTest { every { store.state } returns state every { state.session } returns null every { state.searchEngineSource } returns searchEngine + every { state.showShortcutEnginePicker } returns true val interactor = SearchInteractor(context, mockk(), store) @@ -137,6 +147,10 @@ class SearchInteractorTest { every { context.metrics } returns mockk(relaxed = true) val store: SearchStore = mockk(relaxed = true) + val state: SearchState = mockk(relaxed = true) + + every { store.state } returns state + val interactor = SearchInteractor(context, mockk(), store) val searchEngine: SearchEngine = mockk(relaxed = true) @@ -148,7 +162,11 @@ class SearchInteractorTest { @Test fun onClickSearchEngineSettings() { val navController: NavController = mockk() - val interactor = SearchInteractor(mockk(), navController, mockk()) + val store: SearchStore = mockk() + + every { store.state } returns mockk(relaxed = true) + + val interactor = SearchInteractor(mockk(), navController, store) every { navController.navigate(any() as NavDirections) } just Runs @@ -166,7 +184,9 @@ class SearchInteractorTest { val context: Context = mockk(relaxed = true) val applicationContext: FenixApplication = mockk(relaxed = true) every { context.applicationContext } returns applicationContext - val interactor = SearchInteractor(context, navController, mockk()) + val store: SearchStore = mockk() + every { store.state } returns mockk(relaxed = true) + val interactor = SearchInteractor(context, navController, store) val session = Session("http://mozilla.org", false) interactor.onExistingSessionSelected(session)