commit 199af893683795b60093a321bcb91b92b59fd4c6
parent 7b0c7c8034392fdb8cd13f220973f47927c4bfc4
Author: Akhil Pindiprolu <apindiprolu@mozilla.com>
Date: Wed, 17 Dec 2025 19:13:51 +0000
Bug 1994281 - [Tab Management Search] Add feature flags for releasing the Tab Search feature r=android-reviewers,007
Differential Revision: https://phabricator.services.mozilla.com/D275293
Diffstat:
10 files changed, 118 insertions(+), 36 deletions(-)
diff --git a/mobile/android/fenix/app/nimbus.fml.yaml b/mobile/android/fenix/app/nimbus.fml.yaml
@@ -889,6 +889,25 @@ features:
enabled: true
opening_animation_enabled: false
+ tab-search:
+ description: Control the visibility of the tab search feature.
+ variables:
+ enabled:
+ description: >
+ Whether or not to enable the tab search feature.
+ type: Boolean
+ default: false
+ defaults:
+ - channel: release
+ value:
+ enabled: false
+ - channel: beta
+ value:
+ enabled: false
+ - channel: nightly
+ value:
+ enabled: false
+
suppress-sponsored-top-sites:
description: Suppress sponsored top sites for new users for 14 days.
variables:
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt
@@ -473,6 +473,12 @@ class SecretSettingsFragment : PreferenceFragmentCompat() {
isChecked = context.settings().enablePersistentOnboarding
onPreferenceChangeListener = SharedPreferenceUpdater()
}
+
+ requirePreference<SwitchPreference>(R.string.pref_key_tab_search).apply {
+ isVisible = true
+ isChecked = context.settings().tabSearchEnabled
+ onPreferenceChangeListener = SharedPreferenceUpdater()
+ }
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt
@@ -29,6 +29,7 @@ private const val DEFAULT_SYNCED_TABS_EXPANDED_STATE = true
* @property syncedTabs The list of synced tabs.
* @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired devices.
* @property selectedTabId The ID of the currently selected (active) tab.
+ * @property tabSearchEnabled Whether the tab search feature is enabled.
* @property backStack The navigation history of the Tab Manager feature.
* @property expandedSyncedTabs The list of expansion states for the syncedTabs.
*/
@@ -42,6 +43,7 @@ data class TabsTrayState(
val syncedTabs: List<SyncedTabsListItem> = emptyList(),
val syncing: Boolean = false,
val selectedTabId: String? = null,
+ val tabSearchEnabled: Boolean = false,
val backStack: List<TabManagerNavDestination> = listOf(TabManagerNavDestination.Root),
val expandedSyncedTabs: List<Boolean> = emptyList(),
) : State {
@@ -72,7 +74,7 @@ data class TabsTrayState(
* Whether the Tab Search button is visible.
*/
val searchIconVisible: Boolean
- get() = selectedPage != Page.SyncedTabs
+ get() = tabSearchEnabled && selectedPage != Page.SyncedTabs
/**
* Whether the Tab Search button is enabled.
@@ -330,8 +332,11 @@ internal object TabsTrayReducer {
is TabsTrayAction.CloseAllPrivateTabs -> state
is TabsTrayAction.BookmarkSelectedTabs -> state
is TabsTrayAction.ThreeDotMenuShown -> state
- is TabsTrayAction.TabSearchClicked ->
+
+ is TabsTrayAction.TabSearchClicked -> {
state.copy(backStack = state.backStack + TabManagerNavDestination.TabSearch)
+ }
+
is TabsTrayAction.NavigateBackInvoked -> {
when {
state.mode is TabsTrayState.Mode.Select -> state.copy(mode = TabsTrayState.Mode.Normal)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/TabManagementFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/TabManagementFragment.kt
@@ -174,6 +174,7 @@ class TabManagementFragment : DialogFragment() {
normalTabs = normalTabs,
privateTabs = requireComponents.core.store.state.privateTabs,
selectedTabId = requireComponents.core.store.state.selectedTabId,
+ tabSearchEnabled = requireComponents.settings.tabSearchEnabled,
),
middlewares = listOf(
TabsTrayTelemetryMiddleware(requireComponents.nimbus.events),
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/fab/TabManagerFloatingToolbar.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/fab/TabManagerFloatingToolbar.kt
@@ -49,9 +49,7 @@ import mozilla.components.compose.base.text.Text
import mozilla.components.compose.base.theme.surfaceDimVariant
import mozilla.components.lib.state.ext.observeAsState
import org.mozilla.fenix.R
-import org.mozilla.fenix.tabstray.DefaultTabManagementFeatureHelper
import org.mozilla.fenix.tabstray.Page
-import org.mozilla.fenix.tabstray.TabManagementFeatureHelper
import org.mozilla.fenix.tabstray.TabsTrayAction
import org.mozilla.fenix.tabstray.TabsTrayState
import org.mozilla.fenix.tabstray.TabsTrayState.Mode
@@ -70,7 +68,6 @@ import mozilla.components.ui.icons.R as iconsR
* @param expanded Controls the expansion state of this FAB. In an expanded state, the FAB will
* show both the icon and text. In a collapsed state, the FAB will show only the icon.
* @param pbmLocked Whether the private browsing mode is currently locked.
- * @param featureHelper The feature flag helper for the Tab Manager feature.
* @param onOpenNewNormalTabClicked Invoked when the fab is clicked in [Page.NormalTabs].
* @param onOpenNewPrivateTabClicked Invoked when the fab is clicked in [Page.PrivateTabs].
* @param onSyncedTabsFabClicked Invoked when the fab is clicked in [Page.SyncedTabs].
@@ -87,7 +84,6 @@ internal fun TabManagerFloatingToolbar(
modifier: Modifier = Modifier,
expanded: Boolean = true,
pbmLocked: Boolean = false,
- featureHelper: TabManagementFeatureHelper = DefaultTabManagementFeatureHelper,
onOpenNewNormalTabClicked: () -> Unit,
onOpenNewPrivateTabClicked: () -> Unit,
onSyncedTabsFabClicked: () -> Unit,
@@ -114,7 +110,6 @@ internal fun TabManagerFloatingToolbar(
) {
FloatingToolbarActions(
state = state,
- featureHelper = featureHelper,
onMenuShown = {
tabsTrayStore.dispatch(TabsTrayAction.ThreeDotMenuShown)
},
@@ -154,7 +149,6 @@ internal fun TabManagerFloatingToolbar(
@Composable
private fun FloatingToolbarActions(
state: TabsTrayState,
- featureHelper: TabManagementFeatureHelper,
onMenuShown: () -> Unit,
onEnterMultiselectModeClick: () -> Unit,
onTabSettingsClick: () -> Unit,
@@ -190,7 +184,7 @@ private fun FloatingToolbarActions(
modifier = Modifier.padding(all = FirefoxTheme.layout.space.static100),
horizontalArrangement = Arrangement.spacedBy(FirefoxTheme.layout.space.static50),
) {
- if (featureHelper.tabSearchEnabled && state.searchIconVisible) {
+ if (state.searchIconVisible) {
IconButton(
onClick = onSearchClicked,
modifier = Modifier.testTag(TabsTrayTestTag.TAB_SEARCH_ICON),
@@ -432,77 +426,99 @@ private class TabManagerFloatingToolbarParameterProvider :
PreviewParameterProvider<TabManagerFloatingToolbarPreviewModel> {
override val values: Sequence<TabManagerFloatingToolbarPreviewModel>
get() = sequenceOf(
- // Normal tab page, disabled search icon, collapsed fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.NormalTabs),
+ state = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ tabSearchEnabled = false,
+ normalTabs = listOf(createTab(url = "url")),
+ ),
expanded = false,
),
- // Normal tab page, disabled search icon, expanded fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.NormalTabs),
+ state = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ tabSearchEnabled = false,
+ normalTabs = listOf(createTab(url = "url")),
+ ),
expanded = true,
),
- // Normal tab page, enabled search icon, collapsed fab
TabManagerFloatingToolbarPreviewModel(
state = TabsTrayState(
selectedPage = Page.NormalTabs,
+ tabSearchEnabled = true,
normalTabs = listOf(createTab(url = "url")),
),
expanded = false,
),
- // Normal tab page, enabled search icon, expanded fab
TabManagerFloatingToolbarPreviewModel(
state = TabsTrayState(
selectedPage = Page.NormalTabs,
+ tabSearchEnabled = true,
normalTabs = listOf(createTab(url = "url")),
),
expanded = true,
),
- // Private tab page, disabled search icon, collapsed fab
- TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.PrivateTabs),
- expanded = false,
- ),
- // Private tab page, disabled search icon, expanded fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.PrivateTabs),
+ state = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ tabSearchEnabled = true,
+ normalTabs = emptyList(),
+ ),
expanded = true,
),
- // Private tab page, enabled search icon, collapsed fab
TabManagerFloatingToolbarPreviewModel(
state = TabsTrayState(
selectedPage = Page.PrivateTabs,
- privateTabs = listOf(createTab(url = "url")),
+ tabSearchEnabled = true,
+ privateTabs = listOf(createTab(url = "url", private = true)),
),
expanded = false,
),
- // Private tab page, enabled search icon, expanded fab
TabManagerFloatingToolbarPreviewModel(
state = TabsTrayState(
selectedPage = Page.PrivateTabs,
- privateTabs = listOf(createTab(url = "url")),
+ tabSearchEnabled = true,
+ privateTabs = listOf(createTab(url = "url", private = true)),
),
expanded = true,
),
- // Synced tab page, signed-in, collapsed fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.SyncedTabs),
+ state = TabsTrayState(
+ selectedPage = Page.PrivateTabs,
+ tabSearchEnabled = true,
+ privateTabs = emptyList(),
+ ),
+ expanded = true,
+ ),
+ TabManagerFloatingToolbarPreviewModel(
+ state = TabsTrayState(
+ selectedPage = Page.SyncedTabs,
+ tabSearchEnabled = true,
+ ),
expanded = false,
+ isSignedIn = true,
),
- // Synced tab page, signed-in, expanded fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.SyncedTabs),
+ state = TabsTrayState(
+ selectedPage = Page.SyncedTabs,
+ tabSearchEnabled = true,
+ ),
expanded = true,
+ isSignedIn = true,
),
- // Synced tab page, signed-out, collapsed fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.SyncedTabs),
+ state = TabsTrayState(
+ selectedPage = Page.SyncedTabs,
+ tabSearchEnabled = true,
+ ),
expanded = false,
isSignedIn = false,
),
- // Synced tab page, signed-out, expanded fab
TabManagerFloatingToolbarPreviewModel(
- state = TabsTrayState(selectedPage = Page.SyncedTabs),
+ state = TabsTrayState(
+ selectedPage = Page.SyncedTabs,
+ tabSearchEnabled = true,
+ ),
expanded = true,
isSignedIn = false,
),
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -2828,6 +2828,14 @@ class Settings(
)
/**
+ * Whether the Tab Search feature is enabled.
+ */
+ var tabSearchEnabled by booleanPreference(
+ key = appContext.getPreferenceKey(R.string.pref_key_tab_search),
+ default = { DefaultTabManagementFeatureHelper.tabSearchEnabled },
+ )
+
+ /**
* Indicates whether the app should automatically clean up downloaded files.
*/
fun shouldCleanUpDownloadsAutomatically(): Boolean {
diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml
@@ -520,4 +520,7 @@
<!-- Tab Manager-->
<string name="pref_key_tab_manager_enhancements" translatable="false">pref_key_tab_manager_enhancements</string>
<string name="pref_key_tab_manager_opening_animation" translatable="false">pref_key_tab_manager_opening_animation</string>
+
+ <!-- Tab Search -->
+ <string name="pref_key_tab_search" translatable="false">pref_key_tab_search_feature</string>
</resources>
diff --git a/mobile/android/fenix/app/src/main/res/values/static_strings.xml b/mobile/android/fenix/app/src/main/res/values/static_strings.xml
@@ -122,6 +122,9 @@
<!-- Label for enabling the persistent onboarding -->
<string name="preferences_debug_settings_enable_persistent_onboarding" translatable="false">Show onboarding on each app cold open</string>
+ <!-- Label for toggling the Tab Search feature -->
+ <string name="preferences_tab_search_feature" translatable="false">Enable Tab Search</string>
+
<!-- Label for toggling the Tab Manager enhancements -->
<string name="preferences_tab_manager_enhancements" translatable="false">Enable Tab Manager enhancements</string>
<!-- Label for toggling the Tab Manager opening animation -->
diff --git a/mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml b/mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml
@@ -173,6 +173,10 @@
android:title="@string/preferences_tab_manager_opening_animation"
app:iconSpaceReserved="false" />
<SwitchPreference
+ android:key="@string/pref_key_tab_search"
+ android:title="@string/preferences_tab_search_feature"
+ app:iconSpaceReserved="false" />
+ <SwitchPreference
android:key="@string/pref_key_terms_accepted"
android:title="@string/preferences_terms_of_use_accepted"
app:iconSpaceReserved="false" />
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayStateTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayStateTest.kt
@@ -221,13 +221,28 @@ class TabsTrayStateTest {
@Test
fun `WHEN the user is on the normal tabs page THEN the search icon is visible`() {
- val testState = TabsTrayState(selectedPage = Page.NormalTabs)
+ val testState = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ tabSearchEnabled = true,
+ )
assertTrue(testState.searchIconVisible)
}
@Test
+ fun `GIVEN Tab Search is not enabled WHEN the user is on the normal tabs page THEN the search icon is not visible`() {
+ val testState = TabsTrayState(
+ selectedPage = Page.NormalTabs,
+ tabSearchEnabled = false,
+ )
+ assertFalse(testState.searchIconVisible)
+ }
+
+ @Test
fun `WHEN the user is on the private tabs page THEN the search icon is visible`() {
- val testState = TabsTrayState(selectedPage = Page.PrivateTabs)
+ val testState = TabsTrayState(
+ selectedPage = Page.PrivateTabs,
+ tabSearchEnabled = true,
+ )
assertTrue(testState.searchIconVisible)
}
@@ -255,6 +270,7 @@ class TabsTrayStateTest {
val testState = TabsTrayState(
selectedPage = Page.NormalTabs,
normalTabs = listOf(createTab(url = "url")),
+ tabSearchEnabled = true,
)
assertTrue(testState.searchIconEnabled)
}
@@ -273,6 +289,7 @@ class TabsTrayStateTest {
val testState = TabsTrayState(
selectedPage = Page.PrivateTabs,
privateTabs = listOf(createTab(url = "url")),
+ tabSearchEnabled = true,
)
assertTrue(testState.searchIconEnabled)
}