1884 lines
94 KiB
Diff
1884 lines
94 KiB
Diff
From: uazo <uazo@users.noreply.github.com>
|
|
Date: Tue, 12 Dec 2023 16:38:10 +0000
|
|
Subject: Use list mode for tab switcher
|
|
|
|
Added a setting for the option
|
|
|
|
need: Add-cromite-flags-support.patch
|
|
License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
|
|
---
|
|
chrome/android/features/tab_ui/BUILD.gn | 4 +
|
|
.../res/drawable/selected_tab_background.xml | 13 +
|
|
.../selected_tab_background_incognito.xml | 13 +
|
|
.../java/res/layout/tab_list_card_item.xml | 25 ++
|
|
.../tab_ui/java/res/values/dimens.xml | 5 +
|
|
.../TabGridDialogCoordinator.java | 11 +-
|
|
.../tab_management/TabGridDialogMediator.java | 8 +
|
|
.../TabGridDialogProperties.java | 3 +
|
|
.../tab_management/TabGridDialogView.java | 13 +
|
|
.../TabGridDialogViewBinder.java | 8 +
|
|
.../TabListContainerViewBinder.java | 34 +-
|
|
.../tab_management/TabListCoordinator.java | 37 +-
|
|
.../TabListEditorCoordinator.java | 2 +-
|
|
.../tab_management/TabListItemAnimator.java | 2 +-
|
|
.../tasks/tab_management/TabListMediator.java | 22 +-
|
|
.../tasks/tab_management/TabListView.java | 123 ++++++
|
|
.../tab_management/TabListViewBinder.java | 353 ++++++++++++++++++
|
|
.../tab_management/TabSwitcherPaneBase.java | 4 +-
|
|
.../TabSwitcherPaneCoordinatorFactory.java | 5 +-
|
|
.../tab_management/TabUiFeatureUtilities.java | 7 +
|
|
.../PinnedTabStripCoordinator.java | 10 +-
|
|
.../tab_ui/tab_management_java_sources.gni | 2 +
|
|
.../java/res/xml/privacy_preferences.xml | 6 +
|
|
.../chrome/browser/app/ChromeActivity.java | 5 +-
|
|
chrome/browser/android/favicon_helper.cc | 214 ++++++++++-
|
|
chrome/browser/android/favicon_helper.h | 23 ++
|
|
.../flags/cromite/sTabSwitcherListMode.java | 33 ++
|
|
.../tab_ui/TabListFaviconProvider.java | 111 ++++++
|
|
.../browser/ui/favicon/FaviconHelper.java | 57 ++-
|
|
.../Use-list-mode-for-tab-switcher.grdp | 9 +
|
|
.../browser/toolbar/top/ToolbarPhone.java | 6 +-
|
|
components/browser_ui/styles/android/BUILD.gn | 1 +
|
|
.../java/res/drawable/ic_warning_red_16dp.xml | 26 ++
|
|
.../layout/title_and_description_layout.xml | 31 +-
|
|
.../tab-switcher-list-mode.inc | 3 +
|
|
35 files changed, 1186 insertions(+), 43 deletions(-)
|
|
create mode 100644 chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml
|
|
create mode 100644 chrome/android/features/tab_ui/java/res/drawable/selected_tab_background_incognito.xml
|
|
create mode 100644 chrome/android/features/tab_ui/java/res/layout/tab_list_card_item.xml
|
|
create mode 100644 chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListView.java
|
|
create mode 100644 chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
|
|
create mode 100644 chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sTabSwitcherListMode.java
|
|
create mode 100644 chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Use-list-mode-for-tab-switcher.grdp
|
|
create mode 100644 components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml
|
|
create mode 100644 cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/tab-switcher-list-mode.inc
|
|
|
|
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
|
|
--- a/chrome/android/features/tab_ui/BUILD.gn
|
|
+++ b/chrome/android/features/tab_ui/BUILD.gn
|
|
@@ -52,6 +52,8 @@ android_resources("java_resources") {
|
|
"java/res/drawable/pinned_tab_strip_item_background.xml",
|
|
"java/res/drawable/price_card_background.xml",
|
|
"java/res/drawable/price_card_scrim.xml",
|
|
+ "java/res/drawable/selected_tab_background.xml",
|
|
+ "java/res/drawable/selected_tab_background_incognito.xml",
|
|
"java/res/drawable/tab_grid_card_background.xml",
|
|
"java/res/drawable/tab_grid_card_highlight.xml",
|
|
"java/res/drawable/tab_grid_dialog_background.xml",
|
|
@@ -112,6 +114,8 @@ android_resources("java_resources") {
|
|
"java/res/layout/tab_group_visual_data_dialog.xml",
|
|
"java/res/layout/tab_group_visual_data_text_input_layout.xml",
|
|
"java/res/layout/tab_hover_card_holder.xml",
|
|
+ "java/res/layout/tab_list_card_item.xml",
|
|
+ "java/res/layout/tab_list_card_item.xml",
|
|
"java/res/layout/tab_list_editor_action_view.xml",
|
|
"java/res/layout/tab_list_editor_layout.xml",
|
|
"java/res/layout/tab_list_editor_toolbar.xml",
|
|
diff --git a/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml b/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background.xml
|
|
@@ -0,0 +1,13 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<!--
|
|
+Copyright 2019 The Chromium Authors
|
|
+Use of this source code is governed by a BSD-style license that can be
|
|
+found in the LICENSE file.
|
|
+-->
|
|
+
|
|
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
+ <stroke
|
|
+ android:width="2dp"
|
|
+ android:color="@macro/default_control_color_active" />
|
|
+ <corners android:radius="@dimen/tab_list_card_radius" />
|
|
+</shape>
|
|
diff --git a/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background_incognito.xml b/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background_incognito.xml
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/android/features/tab_ui/java/res/drawable/selected_tab_background_incognito.xml
|
|
@@ -0,0 +1,13 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<!--
|
|
+Copyright 2019 The Chromium Authors
|
|
+Use of this source code is governed by a BSD-style license that can be
|
|
+found in the LICENSE file.
|
|
+-->
|
|
+
|
|
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
+ <stroke
|
|
+ android:width="2dp"
|
|
+ android:color="@color/baseline_primary_80" />
|
|
+ <corners android:radius="@dimen/tab_list_card_radius" />
|
|
+</shape>
|
|
diff --git a/chrome/android/features/tab_ui/java/res/layout/tab_list_card_item.xml b/chrome/android/features/tab_ui/java/res/layout/tab_list_card_item.xml
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/android/features/tab_ui/java/res/layout/tab_list_card_item.xml
|
|
@@ -0,0 +1,25 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<!--
|
|
+Copyright 2019 The Chromium Authors
|
|
+Use of this source code is governed by a BSD-style license that can be
|
|
+found in the LICENSE file.
|
|
+-->
|
|
+<org.chromium.chrome.browser.tasks.tab_management.TabListView
|
|
+ xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
+ xmlns:tools="http://schemas.android.com/tools"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="wrap_content">
|
|
+
|
|
+ <LinearLayout
|
|
+ android:id="@+id/content_view"
|
|
+ android:layout_width="match_parent"
|
|
+ android:layout_height="match_parent"
|
|
+ android:orientation="horizontal"
|
|
+ android:background="@drawable/menu_bg_tinted">
|
|
+
|
|
+ <include layout="@layout/modern_list_item_view" />
|
|
+
|
|
+ </LinearLayout>
|
|
+
|
|
+</org.chromium.chrome.browser.tasks.tab_management.TabListView>
|
|
diff --git a/chrome/android/features/tab_ui/java/res/values/dimens.xml b/chrome/android/features/tab_ui/java/res/values/dimens.xml
|
|
--- a/chrome/android/features/tab_ui/java/res/values/dimens.xml
|
|
+++ b/chrome/android/features/tab_ui/java/res/values/dimens.xml
|
|
@@ -6,8 +6,10 @@ found in the LICENSE file.
|
|
-->
|
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
<dimen name="tab_list_selected_inset">7dp</dimen>
|
|
+ <dimen name="tab_list_selected_inset_low_end">6dp</dimen>
|
|
<dimen name="tab_list_selected_margin">7dp</dimen>
|
|
<dimen name="tab_grid_price_card_radius">4dp</dimen>
|
|
+ <dimen name="tab_list_card_radius">4dp</dimen>
|
|
<dimen name="tab_list_mini_card_radius">4dp</dimen>
|
|
<dimen name="tab_list_mini_card_frame_size">1dp</dimen>
|
|
<dimen name="tab_grid_close_button_size">18dp</dimen>
|
|
@@ -43,6 +45,8 @@ found in the LICENSE file.
|
|
<dimen name="swipe_to_dismiss_threshold">144dp</dimen>
|
|
<dimen name="long_press_cancel_threshold">20dp</dimen>
|
|
<dimen name="selection_tab_grid_toggle_button_inset">14dp</dimen>
|
|
+ <dimen name="selection_tab_list_toggle_button_lateral_inset">18dp</dimen>
|
|
+ <dimen name="selection_tab_list_toggle_button_vertical_inset">22dp</dimen>
|
|
|
|
<!-- Dimens for incognito reauth promo message icon -->
|
|
<dimen name="incognito_reauth_promo_message_icon_width">52dp</dimen>
|
|
@@ -117,6 +121,7 @@ found in the LICENSE file.
|
|
<dimen name="tab_card_label_icon_inset">3dp</dimen>
|
|
<dimen name="tab_card_label_icon_radius">8dp</dimen>
|
|
<dimen name="tab_card_label_height">24dp</dimen>
|
|
+ <dimen name="tab_card_label_list_margin_end">9dp</dimen>
|
|
<dimen name="tab_card_label_elev">6dp</dimen>
|
|
|
|
<!-- Dimens for TabGroupSync features -->
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
|
|
@@ -244,7 +244,9 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogCon
|
|
// take in a mode parameter instead.
|
|
mTabListCoordinator =
|
|
new TabListCoordinator(
|
|
- TabListMode.GRID,
|
|
+ TabUiFeatureUtilities.shouldUseListMode()
|
|
+ ? TabListMode.LIST
|
|
+ : TabListMode.GRID,
|
|
activity,
|
|
mBrowserControlsStateProvider,
|
|
mModalDialogManager,
|
|
@@ -361,6 +363,11 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogCon
|
|
: "SnackbarManager should have been created or the activity was already"
|
|
+ " finishing.";
|
|
|
|
+ @TabListMode
|
|
+ int mode =
|
|
+ TabUiFeatureUtilities.shouldUseListMode()
|
|
+ ? TabListMode.LIST
|
|
+ : TabListMode.GRID;
|
|
ViewGroup container = mDialogView.findViewById(R.id.dialog_container_view);
|
|
mTabListEditorCoordinator =
|
|
new TabListEditorCoordinator(
|
|
@@ -371,7 +378,7 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogCon
|
|
mCurrentTabGroupModelFilterSupplier,
|
|
mTabContentManager,
|
|
mTabListCoordinator::setRecyclerViewPosition,
|
|
- TabListMode.GRID,
|
|
+ mode,
|
|
/* displayGroups= */ false,
|
|
mSnackbarManager,
|
|
mBottomSheetController,
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
|
|
@@ -55,6 +55,7 @@ import org.chromium.chrome.browser.tab.TabLaunchType;
|
|
import org.chromium.chrome.browser.tab.TabSelectionType;
|
|
import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
|
|
import org.chromium.chrome.browser.tab_ui.RecyclerViewPosition;
|
|
+import org.chromium.chrome.browser.tab_ui.TabCardThemeUtil;
|
|
import org.chromium.chrome.browser.tabmodel.TabClosingSource;
|
|
import org.chromium.chrome.browser.tabmodel.TabClosureParamsUtils;
|
|
import org.chromium.chrome.browser.tabmodel.TabCreatorUtil;
|
|
@@ -900,6 +901,13 @@ public class TabGridDialogMediator
|
|
TabGridDialogProperties.DIALOG_UNGROUP_BAR_HOVERED_TEXT_COLOR,
|
|
ungroupBarHoveredTextColor);
|
|
mModel.set(TabGridDialogProperties.IS_INCOGNITO, isIncognito);
|
|
+ if (TabUiFeatureUtilities.shouldUseListMode()) {
|
|
+ int animationBackgroundColor =
|
|
+ TabCardThemeUtil.getCardViewBackgroundColor(
|
|
+ mActivity, isIncognito, /* isSelected= */ false, /*color*/ null);
|
|
+ mModel.set(
|
|
+ TabGridDialogProperties.ANIMATION_BACKGROUND_COLOR, animationBackgroundColor);
|
|
+ }
|
|
}
|
|
|
|
private void updateDialogScrollPosition() {
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogProperties.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogProperties.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogProperties.java
|
|
@@ -65,6 +65,8 @@ class TabGridDialogProperties {
|
|
new WritableIntPropertyKey();
|
|
public static final WritableObjectPropertyKey<String> DIALOG_UNGROUP_BAR_TEXT =
|
|
new WritableObjectPropertyKey<>();
|
|
+ public static final WritableObjectPropertyKey<Integer> ANIMATION_BACKGROUND_COLOR =
|
|
+ new WritableObjectPropertyKey<>();
|
|
|
|
/**
|
|
* Integer, but not {@link WritableIntPropertyKey} so that we can force update on the same
|
|
@@ -147,6 +149,7 @@ class TabGridDialogProperties {
|
|
TAB_GROUP_COLOR_ID,
|
|
IS_INCOGNITO,
|
|
COLOR_ICON_CLICK_LISTENER,
|
|
+ ANIMATION_BACKGROUND_COLOR,
|
|
HAIRLINE_COLOR,
|
|
HAIRLINE_VISIBILITY,
|
|
FORCE_ANIMATION_TO_FINISH,
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogView.java
|
|
@@ -1141,6 +1141,19 @@ public class TabGridDialogView extends FrameLayout {
|
|
mHairline.setVisibility(visible ? VISIBLE : GONE);
|
|
}
|
|
|
|
+ /**
|
|
+ * Updates the background color for the animation card.
|
|
+ *
|
|
+ * @param colorInt The new color to use.
|
|
+ */
|
|
+ void updateAnimationBackgroundColor(@ColorInt int colorInt) {
|
|
+ assert TabUiFeatureUtilities.shouldUseListMode();
|
|
+ updateAnimationCardView(null);
|
|
+ Drawable animationBackground =
|
|
+ mAnimationCardView.findViewById(R.id.card_view).getBackground();
|
|
+ DrawableCompat.setTint(animationBackground, colorInt);
|
|
+ }
|
|
+
|
|
/**
|
|
* Update the ungroup bar background color.
|
|
*
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewBinder.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewBinder.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogViewBinder.java
|
|
@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.tasks.tab_management;
|
|
|
|
import static org.chromium.build.NullUtil.assumeNonNull;
|
|
import static org.chromium.chrome.browser.tasks.tab_management.TabGridDialogProperties.ADD_CLICK_LISTENER;
|
|
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridDialogProperties.ANIMATION_BACKGROUND_COLOR;
|
|
import static org.chromium.chrome.browser.tasks.tab_management.TabGridDialogProperties.ANIMATION_SOURCE_VIEW;
|
|
import static org.chromium.chrome.browser.tasks.tab_management.TabGridDialogProperties.APP_HEADER_HEIGHT;
|
|
import static org.chromium.chrome.browser.tasks.tab_management.TabGridDialogProperties.BROWSER_CONTROLS_STATE_PROVIDER;
|
|
@@ -227,6 +228,13 @@ class TabGridDialogViewBinder {
|
|
} else if (COLOR_ICON_CLICK_LISTENER == propertyKey) {
|
|
viewHolder.toolbarView.setColorIconOnClickListener(
|
|
model.get(COLOR_ICON_CLICK_LISTENER));
|
|
+ } else if (ANIMATION_BACKGROUND_COLOR == propertyKey) {
|
|
+ // Only set in LIST mode not GRID mode. Will always be set in LIST mode. Mode is not
|
|
+ // mutable without restarting the app.
|
|
+ if (model.get(ANIMATION_BACKGROUND_COLOR) != null) {
|
|
+ viewHolder.dialogView.updateAnimationBackgroundColor(
|
|
+ model.get(ANIMATION_BACKGROUND_COLOR));
|
|
+ }
|
|
} else if (FORCE_ANIMATION_TO_FINISH == propertyKey) {
|
|
if (model.get(FORCE_ANIMATION_TO_FINISH)) {
|
|
viewHolder.dialogView.forceAnimationToFinish();
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinder.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinder.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinder.java
|
|
@@ -86,10 +86,7 @@ class TabListContainerViewBinder {
|
|
mTabListRecyclerView = tabListRecyclerView;
|
|
mPaneHairline = paneHairline;
|
|
mSupplementaryContainer = supplementaryContainer;
|
|
- mSearchBoxGapPx =
|
|
- tabListRecyclerView
|
|
- .getResources()
|
|
- .getDimensionPixelSize(R.dimen.hub_search_box_gap);
|
|
+ mSearchBoxGapPx = 0;
|
|
}
|
|
}
|
|
|
|
@@ -305,14 +302,27 @@ class TabListContainerViewBinder {
|
|
}
|
|
if (width <= 0 || height <= 0) return 0;
|
|
|
|
+ @TabListMode int mode = model.get(MODE);
|
|
LinearLayoutManager layoutManager = (LinearLayoutManager) view.getLayoutManager();
|
|
- assert model.get(MODE) == TabListMode.GRID;
|
|
- GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
|
|
- assumeNonNull(gridLayoutManager);
|
|
- int cardWidth = width / gridLayoutManager.getSpanCount();
|
|
- int cardHeight =
|
|
- TabUtils.deriveGridCardHeight(
|
|
- cardWidth, view.getContext(), browserControlsStateProvider);
|
|
- return Math.max(0, height / 2 - cardHeight / 2);
|
|
+ if (mode == TabListMode.GRID) {
|
|
+ GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
|
|
+ assumeNonNull(gridLayoutManager);
|
|
+ int cardWidth = width / gridLayoutManager.getSpanCount();
|
|
+ int cardHeight =
|
|
+ TabUtils.deriveGridCardHeight(
|
|
+ cardWidth, view.getContext(), browserControlsStateProvider);
|
|
+ return Math.max(0, height / 2 - cardHeight / 2);
|
|
+ }
|
|
+ if (mode == TabListMode.LIST) {
|
|
+ // Avoid divide by 0 when there are no tabs.
|
|
+ if (layoutManager.getItemCount() == 0) return 0;
|
|
+
|
|
+ return Math.max(
|
|
+ 0,
|
|
+ height / 2
|
|
+ - view.computeVerticalScrollRange() / layoutManager.getItemCount() / 2);
|
|
+ }
|
|
+ assert false : "Unexpected MODE when setting INITIAL_SCROLL_INDEX.";
|
|
+ return 0;
|
|
}
|
|
}
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
|
|
@@ -122,13 +122,13 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
*
|
|
* <p>NOTE: STRIP and GRID modes will have height equal to that of the container view.
|
|
*/
|
|
- @IntDef({TabListMode.GRID, TabListMode.STRIP, TabListMode.NUM_ENTRIES})
|
|
+ @IntDef({TabListMode.GRID, TabListMode.STRIP, TabListMode.LIST, TabListMode.NUM_ENTRIES})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface TabListMode {
|
|
int GRID = 0;
|
|
int STRIP = 1;
|
|
// int CAROUSEL_DEPRECATED = 2;
|
|
- // int LIST_DEPRECATED = 3;
|
|
+ int LIST = 3;
|
|
int NUM_ENTRIES = 4;
|
|
}
|
|
|
|
@@ -374,6 +374,8 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
assumeNonNull(model);
|
|
if (mMode == TabListMode.GRID) {
|
|
TabGridViewBinder.onViewRecycled(model, viewHolder.itemView);
|
|
+ } else if (mMode == TabListMode.LIST) {
|
|
+ TabListViewBinder.onViewRecycled(model, viewHolder.itemView);
|
|
} else if (mMode == TabListMode.STRIP) {
|
|
TabStripViewBinder.onViewRecycled(model, viewHolder.itemView);
|
|
}
|
|
@@ -446,6 +448,21 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
.inflate(R.layout.tab_strip_item, parentView, false);
|
|
},
|
|
TabStripViewBinder::bind);
|
|
+ } else if (mMode == TabListMode.LIST) {
|
|
+ mAdapter.registerType(
|
|
+ UiType.TAB,
|
|
+ parent -> {
|
|
+ ViewLookupCachingFrameLayout group =
|
|
+ (ViewLookupCachingFrameLayout)
|
|
+ LayoutInflater.from(activity)
|
|
+ .inflate(
|
|
+ R.layout.tab_list_card_item,
|
|
+ parentView,
|
|
+ false);
|
|
+ group.setClickable(true);
|
|
+ return group;
|
|
+ },
|
|
+ TabListViewBinder::bindTab);
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Attempting to create a tab list UI with invalid mode");
|
|
@@ -508,7 +525,7 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
parentView.addView(mRecyclerView);
|
|
}
|
|
|
|
- // GRID has a fixed size. STRIP has a fixed size only if DATA_SHARING is off.
|
|
+ // GRID and LIST both have fixed size. STRIP has a fixed size only if DATA_SHARING is off.
|
|
boolean hasFixedSize =
|
|
mMode != TabListMode.STRIP || !TabUiUtils.isDataSharingFunctionalityEnabled();
|
|
mRecyclerView.setAdapter(mAdapter);
|
|
@@ -540,9 +557,13 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
Rect frame = new Rect();
|
|
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
|
|
updateGridCardLayout(frame.width());
|
|
- } else if (mMode == TabListMode.STRIP) {
|
|
+ } else if (mMode == TabListMode.STRIP /*|| mMode == TabListMode.LIST*/) {
|
|
LinearLayoutManager layoutManager =
|
|
- new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) {
|
|
+ new LinearLayoutManager(activity,
|
|
+ mMode == TabListMode.LIST
|
|
+ ? LinearLayoutManager.VERTICAL
|
|
+ : LinearLayoutManager.HORIZONTAL,
|
|
+ false) {
|
|
@Override
|
|
public void onLayoutCompleted(RecyclerView.State state) {
|
|
super.onLayoutCompleted(state);
|
|
@@ -684,7 +705,7 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
public void addTabListItemSizeChangedObserver(TabListItemSizeChangedObserver observer) {
|
|
mTabListItemSizeChangedObserverList.addObserver(observer);
|
|
Size size = mMediator.getDefaultGridCardSize();
|
|
- assert size != null;
|
|
+ if (size == null) return;
|
|
observer.onSizeChanged(mMediator.getCurrentSpanCount(), size);
|
|
}
|
|
|
|
@@ -719,7 +740,7 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
|
|
Size getThumbnailSize() {
|
|
Size size = mMediator.getDefaultGridCardSize();
|
|
- assert size != null;
|
|
+ if (size == null) return new Size(0, 0);
|
|
return TabUtils.deriveThumbnailSize(size, mActivity);
|
|
}
|
|
|
|
@@ -773,7 +794,7 @@ public class TabListCoordinator implements PriceWelcomeMessageProvider, DestroyO
|
|
}
|
|
|
|
private void configureRecyclerViewTouchHelpers() {
|
|
- boolean modeAllowsDragAndDrop = mMode == TabListMode.GRID;
|
|
+ boolean modeAllowsDragAndDrop = mMode == TabListMode.GRID || mMode == TabListMode.LIST;
|
|
boolean actionStateAllowsDragAndDrop = mTabActionState != TabActionState.SELECTABLE;
|
|
if (mAllowDragAndDrop && modeAllowsDragAndDrop && actionStateAllowsDragAndDrop) {
|
|
if (mOnBeforeItemTouchHelperItemTouchListener == null
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
|
|
@@ -421,7 +421,7 @@ public class TabListEditorCoordinator {
|
|
mSnackbarManager = snackbarManager;
|
|
mTabActionState = initialTabActionState;
|
|
mTabContentManager = tabContentManager;
|
|
- assert mode == TabListCoordinator.TabListMode.GRID;
|
|
+ assert mode == TabListCoordinator.TabListMode.GRID || mode == TabListCoordinator.TabListMode.LIST;
|
|
mGridCardOnClickListenerProvider = gridCardOnClickListenerProvider;
|
|
mModalDialogManager = modalDialogManager;
|
|
mEdgeToEdgeSupplier = edgeToEdgeSupplier;
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListItemAnimator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListItemAnimator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListItemAnimator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListItemAnimator.java
|
|
@@ -435,7 +435,7 @@ public class TabListItemAnimator extends SimpleItemAnimator {
|
|
}
|
|
|
|
Animator animator = null;
|
|
- if (!shouldUseShrinkCloseAnimation(holder)) {
|
|
+ if (TabUiFeatureUtilities.shouldUseListMode() || !shouldUseShrinkCloseAnimation(holder)) {
|
|
animator = buildGenericRemoveAnimator(holder);
|
|
} else {
|
|
animator = buildTabRemoveAnimator(holder);
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
|
|
@@ -2622,8 +2622,26 @@ class TabListMediator implements TabListNotificationHandler {
|
|
private void updateFaviconForTab(
|
|
PropertyModel model, Tab tab, @Nullable Bitmap icon, @Nullable GURL iconUrl) {
|
|
if (mActionsOnAllRelatedTabs && isTabInTabGroup(tab)) {
|
|
- model.set(TabProperties.FAVICON_FETCHER, null);
|
|
- return;
|
|
+ List<Tab> relatedTabList = getRelatedTabsForId(tab.getId());
|
|
+ if (mMode != TabListMode.LIST) {
|
|
+ model.set(TabProperties.FAVICON_FETCHER, null);
|
|
+ return;
|
|
+ } else if (mMode == TabListMode.LIST && relatedTabList.size() > 1) {
|
|
+ // The order of the url list matches the multi-thumbnail.
|
|
+ List<GURL> urls = new ArrayList<>();
|
|
+ urls.add(tab.getUrl());
|
|
+ for (int i = 0; urls.size() < 4 && i < relatedTabList.size(); i++) {
|
|
+ if (tab.getId() == relatedTabList.get(i).getId()) continue;
|
|
+ urls.add(relatedTabList.get(i).getUrl());
|
|
+ }
|
|
+
|
|
+ // For tab group card in list tab switcher, the favicon is the composed favicon.
|
|
+ model.set(
|
|
+ TabProperties.FAVICON_FETCHER,
|
|
+ mTabListFaviconProvider.getComposedFaviconImageFetcher(
|
|
+ urls, tab.isIncognito()));
|
|
+ return;
|
|
+ }
|
|
}
|
|
if (!mTabListFaviconProvider.isInitialized()) {
|
|
return;
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListView.java
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListView.java
|
|
@@ -0,0 +1,123 @@
|
|
+// Copyright 2024 The Chromium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+package org.chromium.chrome.browser.tasks.tab_management;
|
|
+
|
|
+import android.content.Context;
|
|
+import android.graphics.Bitmap;
|
|
+import android.graphics.BitmapFactory;
|
|
+import android.graphics.drawable.Drawable;
|
|
+import android.graphics.drawable.InsetDrawable;
|
|
+import android.util.AttributeSet;
|
|
+import android.view.View;
|
|
+import android.view.accessibility.AccessibilityNodeInfo;
|
|
+import android.widget.ImageView;
|
|
+
|
|
+import androidx.appcompat.content.res.AppCompatResources;
|
|
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
|
+
|
|
+import org.chromium.build.annotations.NullMarked;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
|
|
+import org.chromium.chrome.tab_ui.R;
|
|
+import org.chromium.components.browser_ui.widget.selectable_list.SelectableItemViewBase;
|
|
+
|
|
+// TODO(crbug.com/339038505): De-dupe logic in TabGridView.
|
|
+/** Holds the view for a tab list. */
|
|
+@NullMarked
|
|
+public class TabListView extends SelectableItemViewBase<TabListEditorItemSelectionId> {
|
|
+ private @TabActionState int mTabActionState = TabActionState.UNSET;
|
|
+ private ImageView mActionButton;
|
|
+
|
|
+ public TabListView(Context context, AttributeSet attrs) {
|
|
+ super(context, attrs);
|
|
+ setSelectionOnLongClick(false);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void onFinishInflate() {
|
|
+ super.onFinishInflate();
|
|
+ mActionButton = findViewById(R.id.end_button);
|
|
+ }
|
|
+
|
|
+ void setTabActionState(@TabActionState int tabActionState) {
|
|
+ if (mTabActionState == tabActionState) return;
|
|
+
|
|
+ mTabActionState = tabActionState;
|
|
+ int accessibilityMode = IMPORTANT_FOR_ACCESSIBILITY_YES;
|
|
+ if (mTabActionState == TabActionState.CLOSABLE) {
|
|
+ setTabActionButtonCloseDrawable();
|
|
+ } else if (mTabActionState == TabActionState.SELECTABLE) {
|
|
+ accessibilityMode = IMPORTANT_FOR_ACCESSIBILITY_NO;
|
|
+ setTabActionButtonSelectionDrawable();
|
|
+ }
|
|
+
|
|
+ mActionButton.setImportantForAccessibility(accessibilityMode);
|
|
+ }
|
|
+
|
|
+ private void setTabActionButtonCloseDrawable() {
|
|
+ assert mTabActionState != TabActionState.UNSET;
|
|
+ var resources = getResources();
|
|
+
|
|
+ mActionButton.setVisibility(View.VISIBLE);
|
|
+ int closeButtonSize = (int) resources.getDimension(R.dimen.tab_grid_close_button_size);
|
|
+ Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.btn_close);
|
|
+ Bitmap.createScaledBitmap(bitmap, closeButtonSize, closeButtonSize, true);
|
|
+ mActionButton.setImageBitmap(bitmap);
|
|
+ mActionButton.setBackground(null);
|
|
+ }
|
|
+
|
|
+ private void setTabActionButtonSelectionDrawable() {
|
|
+ assert mTabActionState != TabActionState.UNSET;
|
|
+ var resources = getResources();
|
|
+
|
|
+ Drawable selectionListIcon =
|
|
+ AppCompatResources.getDrawable(
|
|
+ getContext(), R.drawable.tab_grid_selection_list_icon);
|
|
+ mActionButton.setVisibility(View.VISIBLE);
|
|
+ int lateralInset =
|
|
+ resources.getDimensionPixelSize(
|
|
+ R.dimen.selection_tab_list_toggle_button_lateral_inset);
|
|
+ int verticalInset =
|
|
+ resources.getDimensionPixelSize(
|
|
+ R.dimen.selection_tab_list_toggle_button_vertical_inset);
|
|
+ InsetDrawable drawable =
|
|
+ new InsetDrawable(
|
|
+ selectionListIcon,
|
|
+ lateralInset,
|
|
+ verticalInset,
|
|
+ lateralInset,
|
|
+ verticalInset);
|
|
+ mActionButton.setBackground(drawable);
|
|
+ findViewById(R.id.start_icon).setBackground(null);
|
|
+ mActionButton
|
|
+ .getBackground()
|
|
+ .setLevel(resources.getInteger(R.integer.list_item_level_default));
|
|
+ mActionButton.setImageDrawable(
|
|
+ AnimatedVectorDrawableCompat.create(
|
|
+ getContext(), R.drawable.ic_check_googblue_20dp_animated));
|
|
+ }
|
|
+
|
|
+ // SelectableItemViewBase implementation.
|
|
+
|
|
+ @Override
|
|
+ protected void updateView(boolean animate) {}
|
|
+
|
|
+ @Override
|
|
+ protected void handleNonSelectionClick() {}
|
|
+
|
|
+ // TODO(crbug.com/339038201): Consider capturing click events and discarding them while not in
|
|
+ // selection mode.
|
|
+
|
|
+ // View implementation.
|
|
+
|
|
+ @Override
|
|
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
|
+ super.onInitializeAccessibilityNodeInfo(info);
|
|
+
|
|
+ if (mTabActionState == TabActionState.SELECTABLE) {
|
|
+ info.setCheckable(true);
|
|
+ info.setChecked(isChecked());
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java
|
|
@@ -0,0 +1,353 @@
|
|
+// Copyright 2019 The Chromium Authors
|
|
+// Use of this source code is governed by a BSD-style license that can be
|
|
+// found in the LICENSE file.
|
|
+
|
|
+package org.chromium.chrome.browser.tasks.tab_management;
|
|
+
|
|
+import android.content.Context;
|
|
+import android.content.res.ColorStateList;
|
|
+import android.content.res.Resources;
|
|
+import android.graphics.Bitmap;
|
|
+import android.graphics.BitmapFactory;
|
|
+import android.graphics.drawable.Drawable;
|
|
+import android.graphics.drawable.InsetDrawable;
|
|
+import android.view.LayoutInflater;
|
|
+import android.view.View;
|
|
+import android.view.ViewGroup;
|
|
+import android.widget.FrameLayout;
|
|
+import android.widget.ImageView;
|
|
+import android.widget.TextView;
|
|
+
|
|
+import androidx.annotation.ColorInt;
|
|
+import androidx.annotation.DrawableRes;
|
|
+import androidx.annotation.Nullable;
|
|
+import androidx.appcompat.content.res.AppCompatResources;
|
|
+import androidx.core.content.res.ResourcesCompat;
|
|
+import androidx.core.graphics.drawable.DrawableCompat;
|
|
+import androidx.core.view.ViewCompat;
|
|
+import androidx.core.widget.ImageViewCompat;
|
|
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
|
+
|
|
+import org.chromium.chrome.browser.tab_ui.TabListFaviconProvider;
|
|
+import org.chromium.chrome.browser.tab_ui.TabCardThemeUtil;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabActionListener;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabActionButtonData.TabActionButtonType;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
|
|
+import org.chromium.chrome.tab_ui.R;
|
|
+import org.chromium.components.browser_ui.styles.SemanticColorUtils;
|
|
+import org.chromium.ui.modelutil.PropertyKey;
|
|
+import org.chromium.ui.modelutil.PropertyModel;
|
|
+import org.chromium.ui.widget.ViewLookupCachingFrameLayout;
|
|
+
|
|
+/** {@link org.chromium.ui.modelutil.SimpleRecyclerViewMcp.ViewBinder} for tab List. */
|
|
+class TabListViewBinder {
|
|
+ /**
|
|
+ * Main entrypoint for binding TabListView
|
|
+ *
|
|
+ * @param view The view to bind to.
|
|
+ * @param model The model to bind.
|
|
+ */
|
|
+ public static void bindTab(
|
|
+ PropertyModel model, ViewGroup view, @Nullable PropertyKey propertyKey) {
|
|
+ assert view instanceof ViewLookupCachingFrameLayout;
|
|
+ @TabActionState Integer tabActionState = model.get(TabProperties.TAB_ACTION_STATE);
|
|
+ if (tabActionState == null) {
|
|
+ assert false : "TAB_ACTION_STATE must be set before initial bindTab call.";
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ((TabListView) view).setTabActionState(tabActionState);
|
|
+ bindListTab(model, (ViewLookupCachingFrameLayout) view, propertyKey);
|
|
+ if (tabActionState == TabActionState.CLOSABLE) {
|
|
+ bindClosableListTab(model, (ViewLookupCachingFrameLayout) view, propertyKey);
|
|
+ } else if (tabActionState == TabActionState.SELECTABLE) {
|
|
+ bindSelectableListTab(model, (ViewLookupCachingFrameLayout) view, propertyKey);
|
|
+ } else {
|
|
+ assert false : "Unsupported TabActionState provided to bindTab.";
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Handles any cleanup for recycled views that might be expensive to keep around in the pool.
|
|
+ *
|
|
+ * @param model The property model to possibly cleanup.
|
|
+ * @param view The view to possibly cleanup.
|
|
+ */
|
|
+ public static void onViewRecycled(PropertyModel model, View view) {
|
|
+ if (view instanceof TabListView tabListView) {
|
|
+ ImageView faviconView = tabListView.findViewById(R.id.start_icon);
|
|
+ faviconView.setImageDrawable(null);
|
|
+
|
|
+ FrameLayout colorContainer = tabListView.findViewById(R.id.after_title_container);
|
|
+ TabCardViewBinderUtils.detachTabGroupColorView(colorContainer);
|
|
+
|
|
+ FrameLayout labelContainer =
|
|
+ tabListView.findViewById(R.id.before_description_container);
|
|
+ labelContainer.removeAllViews();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // TODO(crbug.com/40107066): Merge with TabGridViewBinder for shared properties.
|
|
+ private static void bindListTab(
|
|
+ PropertyModel model, ViewGroup view, @Nullable PropertyKey propertyKey) {
|
|
+ if (TabProperties.TITLE == propertyKey) {
|
|
+ String title = model.get(TabProperties.TITLE);
|
|
+ ((TextView) view.findViewById(R.id.title)).setText(title);
|
|
+ } else if (TabProperties.FAVICON_FETCHER == propertyKey) {
|
|
+ final TabListFaviconProvider.TabFaviconFetcher fetcher =
|
|
+ model.get(TabProperties.FAVICON_FETCHER);
|
|
+ if (fetcher == null) {
|
|
+ setFavicon(view, null);
|
|
+ return;
|
|
+ }
|
|
+ fetcher.fetch(
|
|
+ tabFavicon -> {
|
|
+ if (fetcher != model.get(TabProperties.FAVICON_FETCHER)) return;
|
|
+
|
|
+ setFavicon(view, tabFavicon.getDefaultDrawable());
|
|
+ });
|
|
+ } else if (TabProperties.IS_SELECTED == propertyKey) {
|
|
+ boolean isSelected = model.get(TabProperties.IS_SELECTED);
|
|
+ boolean isIncognito = model.get(TabProperties.IS_INCOGNITO);
|
|
+ updateColors(view, isIncognito);
|
|
+
|
|
+ @DrawableRes
|
|
+ int selectedTabBackground =
|
|
+ isIncognito
|
|
+ ? R.drawable.selected_tab_background_incognito
|
|
+ : R.drawable.selected_tab_background;
|
|
+ Resources res = view.getResources();
|
|
+ Resources.Theme theme = view.getContext().getTheme();
|
|
+ Drawable drawable =
|
|
+ new InsetDrawable(
|
|
+ ResourcesCompat.getDrawable(res, selectedTabBackground, theme),
|
|
+ (int) res.getDimension(R.dimen.tab_list_selected_inset_low_end));
|
|
+ view.setForeground(isSelected ? drawable : null);
|
|
+ } else if (TabProperties.URL_DOMAIN == propertyKey) {
|
|
+ String domain = model.get(TabProperties.URL_DOMAIN);
|
|
+ ((TextView) view.findViewById(R.id.description)).setText(domain);
|
|
+ } else if (TabProperties.TAB_GROUP_COLOR_VIEW_PROVIDER == propertyKey) {
|
|
+ @Nullable
|
|
+ TabGroupColorViewProvider provider =
|
|
+ model.get(TabProperties.TAB_GROUP_COLOR_VIEW_PROVIDER);
|
|
+ FrameLayout container = view.findViewById(R.id.after_title_container);
|
|
+ TabCardViewBinderUtils.updateTabGroupColorView(container, provider);
|
|
+ } else if (TabProperties.TAB_ACTION_BUTTON_DATA == propertyKey) {
|
|
+ @Nullable TabActionButtonData data = model.get(TabProperties.TAB_ACTION_BUTTON_DATA);
|
|
+ @Nullable
|
|
+ TabActionListener tabActionListener = data == null ? null : data.tabActionListener;
|
|
+ ImageView actionButton = view.findViewById(R.id.end_button);
|
|
+ TabGridViewBinder.setNullableClickListener(tabActionListener, actionButton, model);
|
|
+
|
|
+ if (data == null) return;
|
|
+
|
|
+ Resources res = view.getResources();
|
|
+ if (data.type == TabActionButtonType.OVERFLOW) {
|
|
+ actionButton.setImageDrawable(
|
|
+ ResourcesCompat.getDrawable(
|
|
+ res, R.drawable.ic_more_vert_24dp, view.getContext().getTheme()));
|
|
+ } else if (data.type == TabActionButtonType.CLOSE) {
|
|
+ int closeButtonSize = (int) res.getDimension(R.dimen.tab_grid_close_button_size);
|
|
+ Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.btn_close);
|
|
+ Bitmap.createScaledBitmap(bitmap, closeButtonSize, closeButtonSize, true);
|
|
+ actionButton.setImageBitmap(bitmap);
|
|
+ } else if (data.type == TabActionButtonType.SELECT) {
|
|
+ // Intentional no-op. Handled as part of setTabActionState.
|
|
+ } else {
|
|
+ assert false : "Not reached";
|
|
+ }
|
|
+ } else if (TabProperties.TAB_CLICK_LISTENER == propertyKey) {
|
|
+ TabGridViewBinder.setNullableClickListener(
|
|
+ model.get(TabProperties.TAB_CLICK_LISTENER), view, model);
|
|
+ } else if (TabProperties.TAB_LONG_CLICK_LISTENER == propertyKey) {
|
|
+ TabGridViewBinder.setNullableLongClickListener(
|
|
+ model.get(TabProperties.TAB_LONG_CLICK_LISTENER), view, model);
|
|
+ } else if (TabProperties.TAB_CARD_LABEL_DATA == propertyKey) {
|
|
+ // Ignore this data for tab card labels in selectable mode.
|
|
+ updateTabCardLabel(view, /* tabCardLabelData= */ null);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Bind a closable tab to view.
|
|
+ *
|
|
+ * @param model The model to bind.
|
|
+ * @param view The view to bind to.
|
|
+ * @param propertyKey The property that changed.
|
|
+ */
|
|
+ private static void bindClosableListTab(
|
|
+ PropertyModel model, ViewGroup view, @Nullable PropertyKey propertyKey) {
|
|
+ bindListTab(model, view, propertyKey);
|
|
+
|
|
+ if (TabProperties.IS_SELECTED == propertyKey
|
|
+ || TabProperties.TAB_ACTION_BUTTON_DATA == propertyKey) {
|
|
+ ImageView closeButton = view.findViewById(R.id.end_button);
|
|
+ ImageViewCompat.setImageTintList(
|
|
+ closeButton,
|
|
+ TabCardThemeUtil.getActionButtonTintList(
|
|
+ view.getContext(),
|
|
+ model.get(TabProperties.IS_INCOGNITO),
|
|
+ /* isSelected= */ false,
|
|
+ /* color */ null));
|
|
+ } else if (TabProperties.ACTION_BUTTON_DESCRIPTION_TEXT_RESOLVER == propertyKey) {
|
|
+ TextResolver actionButtonDescriptionTextResolver =
|
|
+ model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_TEXT_RESOLVER);
|
|
+ CharSequence actionButtonDescriptionString =
|
|
+ TabCardViewBinderUtils.resolveNullSafe(
|
|
+ actionButtonDescriptionTextResolver, view.getContext());
|
|
+ view.findViewById(R.id.end_button).setContentDescription(actionButtonDescriptionString);
|
|
+ } else if (TabProperties.TAB_CARD_LABEL_DATA == propertyKey) {
|
|
+ updateTabCardLabel(view, model.get(TabProperties.TAB_CARD_LABEL_DATA));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Bind color updates.
|
|
+ *
|
|
+ * @param view The root view of the item (either Selectable/ClosableTabListView).
|
|
+ * @param isIncognito Whether the model is in incognito mode.
|
|
+ * @param isSelected Whether the item is selected.
|
|
+ */
|
|
+ private static void updateColors(ViewGroup view, boolean isIncognito) {
|
|
+ // TODO(crbug.com/40272756): isSelected is ignored as the selected row is only outlined not
|
|
+ // colored so it should use the unselected color. This will be addressed in a fixit.
|
|
+
|
|
+ // Shared by both classes, from tab_list_card_item.
|
|
+ View contentView = view.findViewById(R.id.content_view);
|
|
+ contentView.getBackground().mutate();
|
|
+ final @ColorInt int backgroundColor =
|
|
+ TabCardThemeUtil.getCardViewBackgroundColor(
|
|
+ view.getContext(), isIncognito, /* isSelected= */ false, /*colorId*/null);
|
|
+ ViewCompat.setBackgroundTintList(contentView, ColorStateList.valueOf(backgroundColor));
|
|
+
|
|
+ final @ColorInt int textColor =
|
|
+ TabCardThemeUtil.getTitleTextColor(
|
|
+ view.getContext(), isIncognito, /* isSelected= */ false, /*colorId*/null);
|
|
+ TextView titleView = view.findViewById(R.id.title);
|
|
+ TextView descriptionView = view.findViewById(R.id.description);
|
|
+ titleView.setTextColor(textColor);
|
|
+ descriptionView.setTextColor(textColor);
|
|
+
|
|
+ ImageView faviconView = view.findViewById(R.id.start_icon);
|
|
+ if (faviconView.getBackground() == null) {
|
|
+ faviconView.setBackgroundResource(R.drawable.list_item_icon_modern_bg);
|
|
+ }
|
|
+ faviconView.getBackground().mutate();
|
|
+ final @ColorInt int faviconBackgroundColor =
|
|
+ TabCardThemeUtil.getMiniThumbnailPlaceholderColor(
|
|
+ view.getContext(), isIncognito, /* isSelected= */ false, /*colorId*/null);
|
|
+ ViewCompat.setBackgroundTintList(
|
|
+ faviconView, ColorStateList.valueOf(faviconBackgroundColor));
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Bind a selectable tab to view.
|
|
+ *
|
|
+ * @param model The model to bind.
|
|
+ * @param view The view to bind to.
|
|
+ * @param propertyKey The property that changed.
|
|
+ */
|
|
+ private static void bindSelectableListTab(
|
|
+ PropertyModel model, ViewGroup view, @Nullable PropertyKey propertyKey) {
|
|
+ bindListTab(model, view, propertyKey);
|
|
+
|
|
+ final int tabId = model.get(TabProperties.TAB_ID);
|
|
+ TabListView tabListView = (TabListView) view;
|
|
+ if (TabProperties.TAB_SELECTION_DELEGATE == propertyKey) {
|
|
+ tabListView.setSelectionDelegate(model.get(TabProperties.TAB_SELECTION_DELEGATE));
|
|
+ tabListView.setItem(TabListEditorItemSelectionId.createTabId(tabId));
|
|
+ } else if (TabProperties.IS_SELECTED == propertyKey
|
|
+ || TabProperties.TAB_ACTION_BUTTON_DATA == propertyKey) {
|
|
+ boolean isSelected = model.get(TabProperties.IS_SELECTED);
|
|
+ boolean isIncognito = model.get(TabProperties.IS_INCOGNITO);
|
|
+ ImageView actionButton = view.findViewById(R.id.end_button);
|
|
+
|
|
+ Context context = view.getContext();
|
|
+ Resources res = view.getResources();
|
|
+ int level = TabCardViewBinderUtils.getCheckmarkLevel(res, isSelected);
|
|
+ ColorStateList backgroundColorStateList =
|
|
+ getBackgroundColorStateList(context, isSelected, isIncognito);
|
|
+
|
|
+ var background = actionButton.getBackground();
|
|
+ background.setLevel(level);
|
|
+ DrawableCompat.setTintList(background.mutate(), backgroundColorStateList);
|
|
+
|
|
+ // The check should be invisible if not selected.
|
|
+ actionButton.getDrawable().setAlpha(isSelected ? 255 : 0);
|
|
+ ImageViewCompat.setImageTintList(
|
|
+ actionButton,
|
|
+ isSelected ? getCheckedDrawableColorStateList(context, isIncognito) : null);
|
|
+ if (isSelected) ((AnimatedVectorDrawableCompat) actionButton.getDrawable()).start();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void setFavicon(View view, Drawable favicon) {
|
|
+ ImageView faviconView = view.findViewById(R.id.start_icon);
|
|
+ faviconView.setImageDrawable(favicon);
|
|
+ }
|
|
+
|
|
+ private static ColorStateList getCheckedDrawableColorStateList(
|
|
+ Context context, boolean isIncognito) {
|
|
+ return ColorStateList.valueOf(
|
|
+ isIncognito
|
|
+ ? context.getColor(R.color.default_icon_color_dark)
|
|
+ : SemanticColorUtils.getDefaultIconColorInverse(context));
|
|
+ }
|
|
+
|
|
+ private static ColorStateList getBackgroundColorStateList(
|
|
+ Context context, boolean isSelected, boolean isIncognito) {
|
|
+ if (isSelected) {
|
|
+ return ColorStateList.valueOf(
|
|
+ isIncognito
|
|
+ ? context.getColor(R.color.baseline_primary_80)
|
|
+ : SemanticColorUtils.getDefaultControlColorActive(context));
|
|
+ } else {
|
|
+ return AppCompatResources.getColorStateList(
|
|
+ context,
|
|
+ isIncognito
|
|
+ ? R.color.default_icon_color_light
|
|
+ : R.color.default_icon_color_tint_list);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void updateTabCardLabel(
|
|
+ ViewGroup view, @Nullable TabCardLabelData tabCardLabelData) {
|
|
+ @Nullable
|
|
+ TabCardLabelView labelView = getOrSetupTabCardLabelView(view, tabCardLabelData == null);
|
|
+ if (labelView != null) {
|
|
+ labelView.setData(tabCardLabelData);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static @Nullable TabCardLabelView getOrSetupTabCardLabelView(
|
|
+ ViewGroup view, boolean isDataNull) {
|
|
+ FrameLayout labelContainer = view.findViewById(R.id.before_description_container);
|
|
+ if (labelContainer.getChildCount() > 0) {
|
|
+ return (TabCardLabelView) labelContainer.getChildAt(0);
|
|
+ } else if (isDataNull) {
|
|
+ // Avoid eagerly creating the view in the event the data is null and it isn't already
|
|
+ // created.
|
|
+ return null;
|
|
+ }
|
|
+ Context context = labelContainer.getContext();
|
|
+ TabCardLabelView labelView =
|
|
+ (TabCardLabelView)
|
|
+ LayoutInflater.from(context)
|
|
+ .inflate(R.layout.tab_card_label_layout, labelContainer, false);
|
|
+ labelContainer.addView(labelView);
|
|
+
|
|
+ Resources res = context.getResources();
|
|
+ int marginEnd = res.getDimensionPixelSize(R.dimen.tab_card_label_list_margin_end);
|
|
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) labelView.getLayoutParams();
|
|
+ params.setMarginEnd(marginEnd);
|
|
+ labelView.setLayoutParams(params);
|
|
+
|
|
+ // TODO(crbug.com/362306803): This is technically supposed to have elevation. However,
|
|
+ // propagating clipToPadding=false and clipChildren=false all the way from
|
|
+ // title_and_description_layout up to tab_list_card_item to make it look right is not worth
|
|
+ // the complexity/risk of impacting other list UI when this UI is likely to be deprecated in
|
|
+ // 2025.
|
|
+ labelView.setElevation(0);
|
|
+ return labelView;
|
|
+ }
|
|
+}
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
|
|
@@ -305,7 +305,7 @@ public abstract class TabSwitcherPaneBase extends PaneBase
|
|
|| isFullSpaceModeOnAndroidXr;
|
|
|
|
@Nullable Tab tab = getCurrentTab();
|
|
- if (tab == null || SysUtils.isLowEndDevice()) {
|
|
+ if (getTabListMode() == TabListMode.LIST || tab == null || SysUtils.isLowEndDevice()) {
|
|
return FadeHubLayoutAnimationFactory.createFadeInAnimatorProvider(
|
|
hubContainerView, HUB_LAYOUT_FADE_DURATION_MS, mOnToolbarAlphaChange);
|
|
} else if (isFullSpaceModeOnAndroidXr && getTabListMode() == TabListMode.GRID) {
|
|
@@ -334,7 +334,7 @@ public abstract class TabSwitcherPaneBase extends PaneBase
|
|
HubContainerView hubContainerView) {
|
|
assert !DeviceFormFactor.isNonMultiDisplayContextOnTablet(hubContainerView.getContext());
|
|
Tab tab = getCurrentTab();
|
|
- if (tab == null || SysUtils.isLowEndDevice()) {
|
|
+ if (getTabListMode() == TabListMode.LIST || tab == null || SysUtils.isLowEndDevice()) {
|
|
return FadeHubLayoutAnimationFactory.createFadeOutAnimatorProvider(
|
|
hubContainerView, HUB_LAYOUT_FADE_DURATION_MS, mOnToolbarAlphaChange);
|
|
}
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
|
|
@@ -150,7 +150,10 @@ public class TabSwitcherPaneCoordinatorFactory {
|
|
mModalDialogManager = modalDialogManager;
|
|
mBottomSheetController = bottomSheetController;
|
|
mDataSharingTabManager = dataSharingTabManager;
|
|
- mMode = TabListCoordinator.TabListMode.GRID;
|
|
+ mMode =
|
|
+ TabUiFeatureUtilities.shouldUseListMode()
|
|
+ ? TabListCoordinator.TabListMode.LIST
|
|
+ : TabListCoordinator.TabListMode.GRID;
|
|
mBackPressManager = backPressManager;
|
|
mDesktopWindowStateManager = desktopWindowStateManager;
|
|
mEdgeToEdgeSupplier = edgeToEdgeSupplier;
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
|
|
@@ -7,6 +7,8 @@ package org.chromium.chrome.browser.tasks.tab_management;
|
|
import android.os.Build;
|
|
|
|
import org.chromium.build.annotations.NullMarked;
|
|
+import org.chromium.chrome.browser.flags.cromite.sTabSwitcherListMode;
|
|
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
|
|
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
@@ -16,6 +18,11 @@ import java.util.Set;
|
|
public class TabUiFeatureUtilities {
|
|
private static final Set<String> TAB_TEARING_OEM_ALLOWLIST = Set.of("samsung");
|
|
|
|
+ /** Returns whether the Grid Tab Switcher UI should use list mode. */
|
|
+ public static boolean shouldUseListMode() {
|
|
+ return sTabSwitcherListMode.getInstance().isEnabled();
|
|
+ }
|
|
+
|
|
/** Returns whether device OEM is allow-listed for tab tearing */
|
|
public static boolean doesOemSupportDragToCreateInstance() {
|
|
return TAB_TEARING_OEM_ALLOWLIST.contains(Build.MANUFACTURER.toLowerCase(Locale.US));
|
|
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/pinned_tabs_strip/PinnedTabStripCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/pinned_tabs_strip/PinnedTabStripCoordinator.java
|
|
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/pinned_tabs_strip/PinnedTabStripCoordinator.java
|
|
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/pinned_tabs_strip/PinnedTabStripCoordinator.java
|
|
@@ -32,6 +32,7 @@ import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator;
|
|
import org.chromium.chrome.browser.tasks.tab_management.TabListModel;
|
|
import org.chromium.chrome.browser.tasks.tab_management.TabListRecyclerView;
|
|
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.UiType;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
|
|
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
|
|
import org.chromium.ui.modaldialog.ModalDialogManager;
|
|
import org.chromium.ui.modelutil.PropertyModel;
|
|
@@ -106,6 +107,10 @@ public class PinnedTabStripCoordinator {
|
|
|
|
RecyclerView tabGridListRecyclerView = tabListCoordinator.getContainerView();
|
|
TabListModel tabListModel = tabListCoordinator.getTabListModel();
|
|
+ if (TabUiFeatureUtilities.shouldUseListMode()) {
|
|
+ mMediator = null;
|
|
+ return;
|
|
+ }
|
|
mMediator =
|
|
createMediator(
|
|
activity,
|
|
@@ -149,11 +154,12 @@ public class PinnedTabStripCoordinator {
|
|
|
|
/** Called when the pinned tabs strip is scrolled. */
|
|
public void onScrolled() {
|
|
- mMediator.onScrolled();
|
|
+ if (mMediator != null) mMediator.onScrolled();
|
|
}
|
|
|
|
/** Returns whether the pinned tabs strip is currently visible. */
|
|
public boolean isPinnedTabsBarVisible() {
|
|
+ if (mMediator == null) return false;
|
|
return mMediator.isPinnedTabsBarVisible();
|
|
}
|
|
|
|
@@ -197,6 +203,6 @@ public class PinnedTabStripCoordinator {
|
|
}
|
|
|
|
public void destroy() {
|
|
- mMediator.destroy();
|
|
+ if (mMediator != null) mMediator.destroy();
|
|
}
|
|
}
|
|
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
|
|
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
|
|
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
|
|
@@ -179,6 +179,8 @@ internal_tab_management_java_sources = [
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListNotificationHandler.java",
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListOnScrollListener.java",
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java",
|
|
+ "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListView.java",
|
|
+ "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewBinder.java",
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java",
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateProvider.java",
|
|
"//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabObjectLabeller.java",
|
|
diff --git a/chrome/android/java/res/xml/privacy_preferences.xml b/chrome/android/java/res/xml/privacy_preferences.xml
|
|
--- a/chrome/android/java/res/xml/privacy_preferences.xml
|
|
+++ b/chrome/android/java/res/xml/privacy_preferences.xml
|
|
@@ -82,6 +82,12 @@ found in the LICENSE file.
|
|
android:summary="@string/allow_external_intent_requests_summary"
|
|
app:featureName="AllowExternalIntentRequests"
|
|
app:needRestart="false" />
|
|
+ <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
|
|
+ android:key="use_tab_switcher_list_mode"
|
|
+ android:title="@string/tab_switcher_list_mode_title"
|
|
+ android:summary="@string/tab_switcher_list_mode_summary"
|
|
+ app:featureName="TabSwitcherListMode"
|
|
+ app:needRestart="true" />
|
|
<PreferenceCategory
|
|
android:key="security_section"
|
|
android:title="@string/security_section_title" />
|
|
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
|
|
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
|
|
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
|
|
@@ -199,6 +199,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
|
|
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
|
|
import org.chromium.chrome.browser.task_manager.TaskManager;
|
|
import org.chromium.chrome.browser.task_manager.TaskManagerFactory;
|
|
+import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
|
|
import org.chromium.chrome.browser.theme.ThemeModuleUtils;
|
|
import org.chromium.chrome.browser.toolbar.ControlContainer;
|
|
import org.chromium.chrome.browser.toolbar.ToolbarManager;
|
|
@@ -700,8 +701,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
|
|
new TabContentManager(
|
|
this,
|
|
mBrowserControlsManagerSupplier.get(),
|
|
- !TabUiFeatureUtilities.shouldUseListMode()
|
|
- && !ContextUtils.getAppSharedPreferences().getBoolean(PrivacySettings.PREF_TABGRID_USE_ICONS, false),
|
|
+ !TabUiFeatureUtilities.shouldUseListMode() &&
|
|
+ !ContextUtils.getAppSharedPreferences().getBoolean(PrivacySettings.PREF_TABGRID_USE_ICONS, false),
|
|
tabModelSelector != null ? tabModelSelector::getTabById : null,
|
|
TabWindowManagerSingleton.getInstance()));
|
|
|
|
diff --git a/chrome/browser/android/favicon_helper.cc b/chrome/browser/android/favicon_helper.cc
|
|
--- a/chrome/browser/android/favicon_helper.cc
|
|
+++ b/chrome/browser/android/favicon_helper.cc
|
|
@@ -45,12 +45,109 @@ using base::android::ConvertUTF8ToJavaString;
|
|
using base::android::JavaRef;
|
|
using base::android::ScopedJavaGlobalRef;
|
|
using base::android::ScopedJavaLocalRef;
|
|
+using JobFinishedCallback = base::OnceCallback<void(void)>;
|
|
|
|
static int64_t JNI_FaviconHelper_Init(JNIEnv* env) {
|
|
return reinterpret_cast<intptr_t>(new FaviconHelper());
|
|
}
|
|
|
|
-FaviconHelper::FaviconHelper() {
|
|
+// This is used by the FaviconHelper::GetComposedFaviconImageInternal, and it is
|
|
+// used to manage multiple FaviconService::GetRawFaviconForPageURL calls. The
|
|
+// number of calls is the size of the urls_. The Job is destroyed after the
|
|
+// number of calls have been reached, and the result_callback_ is finished.
|
|
+class FaviconHelper::Job {
|
|
+ public:
|
|
+ Job(FaviconHelper* favicon_helper,
|
|
+ favicon::FaviconService* favicon_service,
|
|
+ std::vector<GURL> urls,
|
|
+ int desire_size_in_pixel,
|
|
+ JobFinishedCallback job_finished_callback,
|
|
+ favicon_base::FaviconResultsCallback result_callback);
|
|
+
|
|
+ Job(const Job&) = delete;
|
|
+ Job& operator=(const Job&) = delete;
|
|
+
|
|
+ void Start();
|
|
+
|
|
+ private:
|
|
+ void OnFaviconAvailable(int favicon_index,
|
|
+ const favicon_base::FaviconRawBitmapResult& result);
|
|
+ raw_ptr<FaviconHelper> favicon_helper_;
|
|
+ raw_ptr<favicon::FaviconService> favicon_service_;
|
|
+ std::vector<GURL> urls_;
|
|
+ int desire_size_in_pixel_;
|
|
+ JobFinishedCallback job_finished_callback_;
|
|
+ favicon_base::FaviconResultsCallback result_callback_;
|
|
+ int favicon_expected_count_;
|
|
+ std::vector<favicon_base::FaviconRawBitmapResult> favicon_raw_bitmap_results_;
|
|
+ int favicon_result_count_;
|
|
+
|
|
+ base::WeakPtrFactory<Job> weak_ptr_factory_{this};
|
|
+};
|
|
+
|
|
+FaviconHelper::Job::Job(FaviconHelper* favicon_helper,
|
|
+ favicon::FaviconService* favicon_service,
|
|
+ std::vector<GURL> urls,
|
|
+ int desire_size_in_pixel,
|
|
+ JobFinishedCallback job_finished_callback,
|
|
+ favicon_base::FaviconResultsCallback result_callback)
|
|
+ : favicon_helper_(favicon_helper),
|
|
+ favicon_service_(favicon_service),
|
|
+ urls_(urls),
|
|
+ desire_size_in_pixel_(desire_size_in_pixel),
|
|
+ job_finished_callback_(std::move(job_finished_callback)),
|
|
+ result_callback_(std::move(result_callback)),
|
|
+ favicon_raw_bitmap_results_(4),
|
|
+ favicon_result_count_(0) {
|
|
+ favicon_expected_count_ = urls_.size();
|
|
+}
|
|
+
|
|
+void FaviconHelper::Job::Start() {
|
|
+ size_t urls_size = urls_.size();
|
|
+ DCHECK(urls_size > 1 && urls_size <= 4);
|
|
+ if (urls_size <= 1 || urls_size > 4) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (size_t i = 0; i < urls_size; i++) {
|
|
+ favicon_base::FaviconRawBitmapCallback callback =
|
|
+ base::BindOnce(&FaviconHelper::Job::OnFaviconAvailable,
|
|
+ weak_ptr_factory_.GetWeakPtr(), i);
|
|
+
|
|
+ favicon_helper_->GetLocalFaviconImageForURLInternal(
|
|
+ favicon_service_, urls_.at(i), desire_size_in_pixel_,
|
|
+ std::move(callback));
|
|
+ }
|
|
+}
|
|
+
|
|
+void FaviconHelper::Job::OnFaviconAvailable(
|
|
+ int favicon_index,
|
|
+ const favicon_base::FaviconRawBitmapResult& result) {
|
|
+ DCHECK(favicon_index >= 0 && favicon_index < 4);
|
|
+
|
|
+ if (result.is_valid()) {
|
|
+ favicon_raw_bitmap_results_.at(favicon_index) = result;
|
|
+ favicon_result_count_++;
|
|
+ } else {
|
|
+ favicon_expected_count_--;
|
|
+ }
|
|
+
|
|
+ if (favicon_result_count_ == favicon_expected_count_) {
|
|
+ size_t i = 0;
|
|
+ while (i < favicon_raw_bitmap_results_.size()) {
|
|
+ if (!favicon_raw_bitmap_results_[i].is_valid()) {
|
|
+ favicon_raw_bitmap_results_.erase(favicon_raw_bitmap_results_.begin() +
|
|
+ i);
|
|
+ continue;
|
|
+ }
|
|
+ i++;
|
|
+ }
|
|
+ std::move(result_callback_).Run(favicon_raw_bitmap_results_);
|
|
+ std::move(job_finished_callback_).Run();
|
|
+ }
|
|
+}
|
|
+
|
|
+FaviconHelper::FaviconHelper() : last_used_job_id_(0) {
|
|
cancelable_task_tracker_ = std::make_unique<base::CancelableTaskTracker>();
|
|
}
|
|
|
|
@@ -58,6 +155,67 @@ void FaviconHelper::Destroy(JNIEnv* env) {
|
|
delete this;
|
|
}
|
|
|
|
+jboolean FaviconHelper::GetComposedFaviconImage(
|
|
+ JNIEnv* env,
|
|
+ Profile* profile,
|
|
+ const std::vector<GURL>& gurls,
|
|
+ int32_t j_desired_size_in_pixel,
|
|
+ const JavaRef<jobject>&
|
|
+ j_composed_favicon_image_callback) {
|
|
+ DCHECK(profile);
|
|
+ if (!profile) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ favicon::FaviconService* favicon_service =
|
|
+ FaviconServiceFactory::GetForProfile(profile,
|
|
+ ServiceAccessType::EXPLICIT_ACCESS);
|
|
+
|
|
+ DCHECK(favicon_service);
|
|
+ if (!favicon_service) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ int desired_size_in_pixel = static_cast<int>(j_desired_size_in_pixel);
|
|
+
|
|
+ favicon_base::FaviconResultsCallback callback_runner = base::BindOnce(
|
|
+ &FaviconHelper::OnComposedFaviconBitmapResultsAvailable,
|
|
+ weak_ptr_factory_.GetWeakPtr(),
|
|
+ ScopedJavaGlobalRef<jobject>(j_composed_favicon_image_callback),
|
|
+ desired_size_in_pixel);
|
|
+
|
|
+ GetComposedFaviconImageInternal(favicon_service, gurls,
|
|
+ static_cast<int>(j_desired_size_in_pixel),
|
|
+ std::move(callback_runner));
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+void FaviconHelper::GetComposedFaviconImageInternal(
|
|
+ favicon::FaviconService* favicon_service,
|
|
+ std::vector<GURL> urls,
|
|
+ int desired_size_in_pixel,
|
|
+ favicon_base::FaviconResultsCallback callback_runner) {
|
|
+ DCHECK(favicon_service);
|
|
+
|
|
+ JobFinishedCallback job_finished_callback =
|
|
+ base::BindOnce(&FaviconHelper::OnJobFinished,
|
|
+ weak_ptr_factory_.GetWeakPtr(), ++last_used_job_id_);
|
|
+
|
|
+ auto job = std::make_unique<Job>(
|
|
+ this, favicon_service, urls, desired_size_in_pixel,
|
|
+ std::move(job_finished_callback), std::move(callback_runner));
|
|
+
|
|
+ id_to_job_[last_used_job_id_] = std::move(job);
|
|
+ id_to_job_[last_used_job_id_]->Start();
|
|
+}
|
|
+
|
|
+void ::FaviconHelper::OnJobFinished(int job_id) {
|
|
+ DCHECK(id_to_job_.count(job_id));
|
|
+
|
|
+ id_to_job_.erase(job_id);
|
|
+}
|
|
+
|
|
bool FaviconHelper::GetLocalFaviconImageForURL(
|
|
JNIEnv* env,
|
|
Profile* profile,
|
|
@@ -143,6 +301,30 @@ bool FaviconHelper::GetForeignFaviconImageForURL(
|
|
|
|
FaviconHelper::~FaviconHelper() = default;
|
|
|
|
+// Return the index of |sizes| whose area is largest but not exceeds int type
|
|
+// range. If all |sizes|'s area exceed int type range, return the first one.
|
|
+size_t FaviconHelper::GetLargestSizeIndex(const std::vector<gfx::Size>& sizes) {
|
|
+ DCHECK(!sizes.empty());
|
|
+ size_t ret = 0;
|
|
+ // Find the first element whose area doesn't exceed max value, then use it
|
|
+ // to compare with rest elements to find largest size index.
|
|
+ for (size_t i = 0; i < sizes.size(); ++i) {
|
|
+ base::CheckedNumeric<int> checked_area = sizes[i].GetCheckedArea();
|
|
+ if (checked_area.IsValid()) {
|
|
+ ret = i;
|
|
+ int largest_area = checked_area.ValueOrDie();
|
|
+ for (i = ret + 1; i < sizes.size(); ++i) {
|
|
+ int area = sizes[i].GetCheckedArea().ValueOrDefault(-1);
|
|
+ if (largest_area < area) {
|
|
+ ret = i;
|
|
+ largest_area = area;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
void FaviconHelper::OnFaviconBitmapResultAvailable(
|
|
const JavaRef<jobject>& j_favicon_image_callback,
|
|
const favicon_base::FaviconRawBitmapResult& result) {
|
|
@@ -162,4 +344,34 @@ void FaviconHelper::OnFaviconBitmapResultAvailable(
|
|
env, j_favicon_image_callback, j_favicon_bitmap, result.icon_url);
|
|
}
|
|
|
|
+void FaviconHelper::OnComposedFaviconBitmapResultsAvailable(
|
|
+ const JavaRef<jobject>& j_favicon_image_callback,
|
|
+ const int desired_size_in_pixel,
|
|
+ const std::vector<favicon_base::FaviconRawBitmapResult>& results) {
|
|
+ JNIEnv* env = AttachCurrentThread();
|
|
+ std::vector<SkBitmap> result_bitmaps;
|
|
+ std::vector<GURL> icon_url_vector;
|
|
+ for (auto result : results) {
|
|
+ if (!result.is_valid()) {
|
|
+ continue;
|
|
+ }
|
|
+ icon_url_vector.push_back(result.icon_url);
|
|
+ SkBitmap favicon_bitmap = gfx::PNGCodec::Decode(*result.bitmap_data);
|
|
+ result_bitmaps.push_back(std::move(favicon_bitmap));
|
|
+ }
|
|
+ ScopedJavaLocalRef<jobject> j_favicon_bitmap;
|
|
+ if (!result_bitmaps.empty()) {
|
|
+ std::unique_ptr<SkBitmap> composed_bitmap =
|
|
+ compose_bitmaps_helper::ComposeBitmaps(std::move(result_bitmaps),
|
|
+ desired_size_in_pixel);
|
|
+ if (composed_bitmap && !composed_bitmap->isNull()) {
|
|
+ j_favicon_bitmap = gfx::ConvertToJavaBitmap(*composed_bitmap);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Call java side OnComposedFaviconBitmapResultsAvailable method.
|
|
+ Java_ComposedFaviconImageCallback_onComposedFaviconAvailable(
|
|
+ env, j_favicon_image_callback, j_favicon_bitmap, icon_url_vector);
|
|
+}
|
|
+
|
|
DEFINE_JNI(FaviconHelper)
|
|
diff --git a/chrome/browser/android/favicon_helper.h b/chrome/browser/android/favicon_helper.h
|
|
--- a/chrome/browser/android/favicon_helper.h
|
|
+++ b/chrome/browser/android/favicon_helper.h
|
|
@@ -26,6 +26,12 @@ class FaviconHelper {
|
|
FaviconHelper(const FaviconHelper&) = delete;
|
|
FaviconHelper& operator=(const FaviconHelper&) = delete;
|
|
|
|
+ jboolean GetComposedFaviconImage(
|
|
+ JNIEnv* env,
|
|
+ Profile* profile,
|
|
+ const std::vector<GURL>& gurls,
|
|
+ int32_t j_desired_size_in_pixel,
|
|
+ const base::android::JavaRef<jobject>& j_favicon_image_callback);
|
|
bool GetLocalFaviconImageForURL(
|
|
JNIEnv* env,
|
|
Profile* profile,
|
|
@@ -44,11 +50,20 @@ class FaviconHelper {
|
|
GURL url,
|
|
int desired_size_in_pixel,
|
|
favicon_base::FaviconRawBitmapCallback callback_runner);
|
|
+ void GetComposedFaviconImageInternal(
|
|
+ favicon::FaviconService* favicon_service,
|
|
+ std::vector<GURL> urls,
|
|
+ int desired_size_in_pixel,
|
|
+ favicon_base::FaviconResultsCallback callback_runner);
|
|
void OnJobFinished(int job_id);
|
|
|
|
private:
|
|
virtual ~FaviconHelper();
|
|
|
|
+ class Job;
|
|
+
|
|
+ static size_t GetLargestSizeIndex(const std::vector<gfx::Size>& sizes);
|
|
+
|
|
// This function is expected to be bound to a WeakPtr<FaviconHelper>, so that
|
|
// it won't be run if the FaviconHelper is deleted and
|
|
// |j_favicon_image_callback| isn't executed in that case.
|
|
@@ -56,8 +71,16 @@ class FaviconHelper {
|
|
const base::android::JavaRef<jobject>& j_favicon_image_callback,
|
|
const favicon_base::FaviconRawBitmapResult& result);
|
|
|
|
+ void OnComposedFaviconBitmapResultsAvailable(
|
|
+ const base::android::JavaRef<jobject>& j_favicon_image_callback,
|
|
+ const int desired_size_in_pixel,
|
|
+ const std::vector<favicon_base::FaviconRawBitmapResult>& result);
|
|
+
|
|
std::unique_ptr<base::CancelableTaskTracker> cancelable_task_tracker_;
|
|
|
|
+ std::map<int, std::unique_ptr<Job>> id_to_job_;
|
|
+ int last_used_job_id_;
|
|
+
|
|
base::WeakPtrFactory<FaviconHelper> weak_ptr_factory_{this};
|
|
};
|
|
|
|
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sTabSwitcherListMode.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sTabSwitcherListMode.java
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/cromite/sTabSwitcherListMode.java
|
|
@@ -0,0 +1,33 @@
|
|
+/*
|
|
+ This file is part of Cromite.
|
|
+
|
|
+ Cromite is free software: you can redistribute it and/or modify
|
|
+ it under the terms of the GNU General Public License as published by
|
|
+ the Free Software Foundation, either version 3 of the License, or
|
|
+ (at your option) any later version.
|
|
+
|
|
+ Cromite is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ You should have received a copy of the GNU General Public License
|
|
+ along with Cromite. If not, see <https://www.gnu.org/licenses/>.
|
|
+*/
|
|
+
|
|
+package org.chromium.chrome.browser.flags.cromite;
|
|
+
|
|
+import org.chromium.components.cached_flags.CachedFlag;
|
|
+import org.chromium.chrome.browser.flags.ChromeFeatureMap;
|
|
+
|
|
+public class sTabSwitcherListMode {
|
|
+ private static final CachedFlag sInstance =
|
|
+ new CachedFlag(ChromeFeatureMap.getInstance(),
|
|
+ "TabSwitcherListMode", false);
|
|
+
|
|
+ private sTabSwitcherListMode() {}
|
|
+
|
|
+ public static CachedFlag getInstance() {
|
|
+ return sInstance;
|
|
+ }
|
|
+}
|
|
diff --git a/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabListFaviconProvider.java b/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabListFaviconProvider.java
|
|
--- a/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabListFaviconProvider.java
|
|
+++ b/chrome/browser/tab_ui/android/java/src/org/chromium/chrome/browser/tab_ui/TabListFaviconProvider.java
|
|
@@ -35,6 +35,8 @@ import org.chromium.url.GURL;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
+import java.util.Arrays;
|
|
+import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
/** Provider for processed favicons in Tab list. */
|
|
@@ -132,6 +134,28 @@ public class TabListFaviconProvider {
|
|
}
|
|
}
|
|
|
|
+ /** Tracks the GURLS that were used for the composed favicon for the equality check. */
|
|
+ @VisibleForTesting
|
|
+ public static class ComposedTabFavicon extends TabFavicon {
|
|
+ private final GURL[] mGurls;
|
|
+
|
|
+ @VisibleForTesting
|
|
+ public ComposedTabFavicon(Drawable drawable, GURL[] gurls) {
|
|
+ super(drawable, drawable, false);
|
|
+ mGurls = gurls;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ return Arrays.hashCode(mGurls);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean equals(Object obj) {
|
|
+ return (obj instanceof ComposedTabFavicon other) && Arrays.equals(mGurls, other.mGurls);
|
|
+ }
|
|
+ }
|
|
+
|
|
@IntDef({
|
|
StaticTabFaviconType.UNKNOWN,
|
|
StaticTabFaviconType.ROUNDED_GLOBE,
|
|
@@ -252,6 +276,8 @@ public class TabListFaviconProvider {
|
|
private static LazyTabFaviconResolver sRoundedGlobeFavicon;
|
|
private static LazyTabFaviconResolver sRoundedGlobeFaviconForStrip;
|
|
private static LazyTabFaviconResolver sRoundedGlobeFaviconIncognito;
|
|
+ private static LazyTabFaviconResolver sRoundedComposedDefaultFavicon;
|
|
+ private static LazyTabFaviconResolver sRoundedComposedDefaultFaviconIncognito;
|
|
|
|
/** These icons may fail to load. See crbug.com/324996488. */
|
|
private static LazyTabFaviconResolver sRoundedChromeFavicon;
|
|
@@ -423,6 +449,51 @@ public class TabListFaviconProvider {
|
|
};
|
|
}
|
|
|
|
+ /**
|
|
+ * Creates a fetcher that asynchronously creates a composed, up to 4 favicon, {{@link
|
|
+ * TabFavicon}}.
|
|
+ *
|
|
+ * @param urls List of urls, up to 4, whose favicon are requested to be composed.
|
|
+ * @param isIncognito Whether the processed composed favicon is used for incognito or not.
|
|
+ * @return a favicon fetcher that returns the composed favicon.
|
|
+ */
|
|
+ public TabFaviconFetcher getComposedFaviconImageFetcher(List<GURL> urls, boolean isIncognito) {
|
|
+ return new TabFaviconFetcher() {
|
|
+ @Override
|
|
+ public void fetch(Callback<TabFavicon> faviconCallback) {
|
|
+ getComposedFaviconImageAsync(urls, isIncognito, faviconCallback);
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ private void getComposedFaviconImageAsync(
|
|
+ List<GURL> urls, boolean isIncognito, Callback<TabFavicon> faviconCallback) {
|
|
+ assert urls != null && urls.size() > 1 && urls.size() <= 4;
|
|
+ if (mFaviconHelper == null) {
|
|
+ faviconCallback.onResult(getRoundedGlobeFavicon(isIncognito));
|
|
+ return;
|
|
+ }
|
|
+ mFaviconHelper.getComposedFaviconImage(
|
|
+ getProfile(isIncognito),
|
|
+ urls,
|
|
+ mFaviconSize,
|
|
+ (image, iconUrls) -> {
|
|
+ if (image == null) {
|
|
+ faviconCallback.onResult(getDefaultComposedImageFavicon(isIncognito));
|
|
+ } else {
|
|
+ faviconCallback.onResult(
|
|
+ new ComposedTabFavicon(
|
|
+ processBitmap(image, mIsTabStrip), iconUrls));
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private TabFavicon getDefaultComposedImageFavicon(boolean isIncognito) {
|
|
+ return isIncognito
|
|
+ ? sRoundedComposedDefaultFaviconIncognito.get(mContext)
|
|
+ : colorFaviconWithTheme(sRoundedComposedDefaultFavicon.get(mContext));
|
|
+ }
|
|
+
|
|
/** Returns the rounded Chrome favicon to use for native or internal pages. */
|
|
public TabFavicon getRoundedChromeFavicon(boolean isIncognito) {
|
|
if (mIsTabStrip) {
|
|
@@ -785,6 +856,46 @@ public class TabListFaviconProvider {
|
|
StaticTabFaviconType.ROUNDED_CHROME_INCOGNITO);
|
|
});
|
|
}
|
|
+ if (sRoundedComposedDefaultFavicon == null) {
|
|
+ sRoundedComposedDefaultFavicon =
|
|
+ new LazyTabFaviconResolver(
|
|
+ (context) -> {
|
|
+ Bitmap composedBitmap =
|
|
+ getResizedBitmapFromDrawable(
|
|
+ AppCompatResources.getDrawable(
|
|
+ context, R.drawable.ic_warning_red_16dp),
|
|
+ defaultFaviconSize);
|
|
+ return createChromeOwnedResourceTabFavicon(
|
|
+ context,
|
|
+ composedBitmap,
|
|
+ defaultFaviconSize,
|
|
+ cornerRadius,
|
|
+ defaultIconColor,
|
|
+ selectedIconColor,
|
|
+ false,
|
|
+ StaticTabFaviconType.ROUNDED_COMPOSED_DEFAULT);
|
|
+ });
|
|
+ }
|
|
+ if (sRoundedComposedDefaultFaviconIncognito == null) {
|
|
+ sRoundedComposedDefaultFaviconIncognito =
|
|
+ new LazyTabFaviconResolver(
|
|
+ (context) -> {
|
|
+ Bitmap composedBitmap =
|
|
+ getResizedBitmapFromDrawable(
|
|
+ AppCompatResources.getDrawable(
|
|
+ context, R.drawable.ic_warning_red_16dp),
|
|
+ defaultFaviconSize);
|
|
+ return createChromeOwnedResourceTabFavicon(
|
|
+ context,
|
|
+ composedBitmap,
|
|
+ defaultFaviconSize,
|
|
+ cornerRadius,
|
|
+ incognitoIconColor,
|
|
+ incognitoSelectedIconColor,
|
|
+ false,
|
|
+ StaticTabFaviconType.ROUNDED_COMPOSED_DEFAULT_INCOGNITO);
|
|
+ });
|
|
+ }
|
|
|
|
// Tab strip favicons do not recolor when selected.
|
|
if (sRoundedGlobeFaviconForStrip == null) {
|
|
diff --git a/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconHelper.java b/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconHelper.java
|
|
--- a/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconHelper.java
|
|
+++ b/chrome/browser/ui/android/favicon/java/src/org/chromium/chrome/browser/ui/favicon/FaviconHelper.java
|
|
@@ -25,12 +25,15 @@ import org.chromium.chrome.browser.profiles.Profile;
|
|
import org.chromium.components.embedder_support.util.UrlUtilities;
|
|
import org.chromium.url.GURL;
|
|
|
|
+import java.util.List;
|
|
+
|
|
/**
|
|
* This is a helper class to use favicon_service.cc's functionality.
|
|
*
|
|
- * <p>You can request a favicon image by web page URL. Note that an instance of this class should be
|
|
- * created & used & destroyed (by destroy()) in the same thread due to the C++
|
|
- * base::CancelableTaskTracker class requirement.
|
|
+ * You can request a favicon image by web page URL. Note that an instance of
|
|
+ * this class should be created & used & destroyed (by destroy()) in the same
|
|
+ * thread due to the C++ base::CancelableTaskTracker class
|
|
+ * requirement.
|
|
*/
|
|
@NullMarked
|
|
public class FaviconHelper {
|
|
@@ -48,6 +51,17 @@ public class FaviconHelper {
|
|
void onFaviconAvailable(Bitmap image, @JniType("GURL") GURL iconUrl);
|
|
}
|
|
|
|
+ /** Similar to {@link FaviconImageCallback} but with a list of urls used in the image. */
|
|
+ public interface ComposedFaviconImageCallback {
|
|
+ /**
|
|
+ * @param image A composed image that contains some or all of the requested favicons.
|
|
+ * @param iconUrls An ordered array of the icon urls that were used.
|
|
+ */
|
|
+ @CalledByNative("ComposedFaviconImageCallback")
|
|
+ public void onComposedFaviconAvailable(
|
|
+ Bitmap image, @JniType("std::vector<GURL>") GURL[] iconUrls);
|
|
+ }
|
|
+
|
|
/** Helper for generating default favicons and sharing the same icon between multiple views. */
|
|
public static class DefaultFaviconHelper {
|
|
private @Nullable Bitmap mChromeDarkBitmap;
|
|
@@ -230,6 +244,36 @@ public class FaviconHelper {
|
|
faviconImageCallback);
|
|
}
|
|
|
|
+ /**
|
|
+ * Get a composed, up to 4, Favicon bitmap for the requested arguments.
|
|
+ * @param profile Profile used for the FaviconService construction.
|
|
+ * @param urls The list of URLs whose favicon are requested to compose. Size should be 2 to 4.
|
|
+ * @param desiredSizeInPixel The size of the favicon in pixel we want to get.
|
|
+ * @param composedFaviconImageCallback A method to be called back when the result is available.
|
|
+ * Note that this callback is not called if this method returns false.
|
|
+ * @return True if GetLocalFaviconImageForURL is successfully called.
|
|
+ */
|
|
+ public boolean getComposedFaviconImage(
|
|
+ Profile profile,
|
|
+ List<GURL> urls,
|
|
+ int desiredSizeInPixel,
|
|
+ ComposedFaviconImageCallback composedFaviconImageCallback) {
|
|
+ assert mNativeFaviconHelper != 0;
|
|
+
|
|
+ if (urls.size() <= 1 || urls.size() > 4) {
|
|
+ throw new IllegalStateException(
|
|
+ "Only able to compose 2 to 4 favicon, but requested " + urls.size());
|
|
+ }
|
|
+
|
|
+ return FaviconHelperJni.get()
|
|
+ .getComposedFaviconImage(
|
|
+ mNativeFaviconHelper,
|
|
+ profile,
|
|
+ urls.toArray(new GURL[0]),
|
|
+ desiredSizeInPixel,
|
|
+ composedFaviconImageCallback);
|
|
+ }
|
|
+
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
|
@NativeMethods
|
|
public interface Natives {
|
|
@@ -237,6 +281,13 @@ public class FaviconHelper {
|
|
|
|
void destroy(long nativeFaviconHelper);
|
|
|
|
+ boolean getComposedFaviconImage(
|
|
+ long nativeFaviconHelper,
|
|
+ @JniType("Profile*") Profile profile,
|
|
+ @JniType("std::vector<GURL>") GURL[] urls,
|
|
+ int desiredSizeInDip,
|
|
+ ComposedFaviconImageCallback composedFaviconImageCallback);
|
|
+
|
|
boolean getLocalFaviconImageForURL(
|
|
long nativeFaviconHelper,
|
|
@JniType("Profile*") Profile profile,
|
|
diff --git a/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Use-list-mode-for-tab-switcher.grdp b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Use-list-mode-for-tab-switcher.grdp
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/chrome/browser/ui/android/strings/cromite_android_chrome_strings_grd/Use-list-mode-for-tab-switcher.grdp
|
|
@@ -0,0 +1,9 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<grit-part>
|
|
+ <message name="IDS_TAB_SWITCHER_LIST_MODE_TITLE" desc="Text for 'Use list mode for tab switcher' settings-privacy option.">
|
|
+ Use list mode for tab switcher
|
|
+ </message>
|
|
+ <message name="IDS_TAB_SWITCHER_LIST_MODE_SUMMARY" desc="Summary text for 'Use list mode for tab switcher' settings-privacy option.">
|
|
+ Activates the list view of tabs in the tab switcher
|
|
+ </message>
|
|
+</grit-part>
|
|
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
|
|
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
|
|
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
|
|
@@ -69,6 +69,7 @@ import org.chromium.build.annotations.Nullable;
|
|
import org.chromium.chrome.browser.flags.ChromeFeatureList;
|
|
import org.chromium.chrome.browser.ntp_customization.NtpCustomizationUtils;
|
|
import org.chromium.chrome.browser.omnibox.LocationBar;
|
|
+import org.chromium.chrome.browser.flags.cromite.sTabSwitcherListMode;
|
|
import org.chromium.chrome.browser.omnibox.LocationBarBackgroundDrawable;
|
|
import org.chromium.chrome.browser.omnibox.LocationBarBackgroundDrawable.HairlineBehavior;
|
|
import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
|
|
@@ -2140,7 +2141,7 @@ public class ToolbarPhone extends ToolbarLayout
|
|
// On entering the tab switcher, set the focusability of the url bar to be false. This will
|
|
// occur at the start of the enter event, and will later be reset to true upon finishing the
|
|
// exit event.
|
|
- if (inTabSwitcherMode) {
|
|
+ if (inTabSwitcherMode && !sTabSwitcherListMode.getInstance().isEnabled()) {
|
|
mLocationBar.setUrlBarFocusable(false);
|
|
}
|
|
|
|
@@ -2368,7 +2369,8 @@ public class ToolbarPhone extends ToolbarLayout
|
|
ToggleTabStackButtonCoordinator tabSwitcherButtonCoordinator =
|
|
getTabSwitcherButtonCoordinator();
|
|
assumeNonNull(tabSwitcherButtonCoordinator);
|
|
- tabSwitcherButtonCoordinator.getContainerView().setClickable(!hasFocus);
|
|
+ if (!sTabSwitcherListMode.getInstance().isEnabled())
|
|
+ tabSwitcherButtonCoordinator.getContainerView().setClickable(!hasFocus);
|
|
mHomeButton.setClickable(!hasFocus);
|
|
triggerUrlFocusAnimation(hasFocus);
|
|
}
|
|
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
|
|
--- a/components/browser_ui/styles/android/BUILD.gn
|
|
+++ b/components/browser_ui/styles/android/BUILD.gn
|
|
@@ -247,6 +247,7 @@ android_resources("java_resources") {
|
|
"java/res/drawable/ic_visibility_off_black.xml",
|
|
"java/res/drawable/ic_volume_off_fill_24dp.xml",
|
|
"java/res/drawable/ic_volume_up_fill_24dp.xml",
|
|
+ "java/res/drawable/ic_warning_red_16dp.xml",
|
|
"java/res/drawable/ic_warning_red_24dp.xml",
|
|
"java/res/drawable/ic_warning_red_outline_32dp.xml",
|
|
"java/res/drawable/ic_web_24dp.xml",
|
|
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_warning_red_16dp.xml
|
|
@@ -0,0 +1,26 @@
|
|
+<?xml version="1.0" encoding="utf-8"?>
|
|
+<!--
|
|
+Copyright 2017 The Chromium Authors
|
|
+Use of this source code is governed by a BSD-style license that can be
|
|
+found in the LICENSE file.
|
|
+-->
|
|
+
|
|
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
+ android:width="16dp"
|
|
+ android:height="16dp"
|
|
+ android:viewportWidth="16"
|
|
+ android:viewportHeight="16">
|
|
+
|
|
+ <path
|
|
+ android:pathData="M -1 -1 H 17 V 17 H -1 V -1 Z" />
|
|
+ <path
|
|
+ android:fillAlpha="0"
|
|
+ android:fillType="evenOdd"
|
|
+ android:strokeAlpha="0"
|
|
+ android:strokeWidth="1"
|
|
+ android:pathData="M 0 0 H 16 V 16 H 0 V 0 Z" />
|
|
+ <path
|
|
+ android:fillColor="@color/google_red_600"
|
|
+ android:fillType="evenOdd"
|
|
+ android:pathData="M0.5,14l15,0l-7.5,-13l-7.5,13l0,0zm8.5,-2l-2,0l0,-2l2,0l0,2l0,0zm0,-3l-2,0l0,-3l2,0l0,3l0,0z" />
|
|
+</vector>
|
|
diff --git a/components/browser_ui/widget/android/java/res/layout/title_and_description_layout.xml b/components/browser_ui/widget/android/java/res/layout/title_and_description_layout.xml
|
|
--- a/components/browser_ui/widget/android/java/res/layout/title_and_description_layout.xml
|
|
+++ b/components/browser_ui/widget/android/java/res/layout/title_and_description_layout.xml
|
|
@@ -25,9 +25,22 @@ found in the LICENSE file.
|
|
app:layout_constrainedWidth="true"
|
|
app:layout_constraintStart_toStartOf="parent"
|
|
app:layout_constraintTop_toTopOf="parent"
|
|
- app:layout_constraintEnd_toStartOf="@+id/space_anchor"
|
|
+ app:layout_constraintEnd_toStartOf="@+id/after_title_container"
|
|
app:layout_constraintWidth_default="wrap" />
|
|
|
|
+ <FrameLayout
|
|
+ android:id="@+id/after_title_container"
|
|
+ android:layout_marginStart="6dp"
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ android:layout_gravity="center_vertical"
|
|
+ android:visibility="gone"
|
|
+ app:layout_constraintVertical_bias="0.5"
|
|
+ app:layout_constraintBottom_toBottomOf="@+id/title"
|
|
+ app:layout_constraintTop_toTopOf="@+id/title"
|
|
+ app:layout_constraintStart_toEndOf="@+id/title"
|
|
+ app:layout_constraintEnd_toStartOf="@+id/space_anchor" />
|
|
+
|
|
<!-- Anchor to ensure the chain terminates without any elements being pushed offscreen. -->
|
|
<Space
|
|
android:id="@+id/space_anchor"
|
|
@@ -35,6 +48,18 @@ found in the LICENSE file.
|
|
android:layout_height="match_parent"
|
|
app:layout_constraintEnd_toEndOf="parent" />
|
|
|
|
+ <FrameLayout
|
|
+ android:id="@+id/before_description_container"
|
|
+ android:layout_gravity="center_vertical|start"
|
|
+ android:layout_width="wrap_content"
|
|
+ android:layout_height="wrap_content"
|
|
+ app:layout_constraintHorizontal_bias="0.0"
|
|
+ app:layout_constraintHorizontal_chainStyle="packed"
|
|
+ app:layout_constraintTop_toBottomOf="@+id/title"
|
|
+ app:layout_constraintBottom_toBottomOf="@+id/description"
|
|
+ app:layout_constraintStart_toStartOf="parent"
|
|
+ app:layout_constraintEnd_toStartOf="@+id/description" />
|
|
+
|
|
<TextView
|
|
android:id="@+id/description"
|
|
android:layout_gravity="center_vertical|start"
|
|
@@ -43,10 +68,8 @@ found in the LICENSE file.
|
|
android:maxLines="1"
|
|
android:ellipsize="end"
|
|
android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
|
|
- app:layout_constraintHorizontal_bias="0.0"
|
|
- app:layout_constraintHorizontal_chainStyle="packed"
|
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
|
- app:layout_constraintStart_toStartOf="parent"
|
|
+ app:layout_constraintStart_toEndOf="@+id/before_description_container"
|
|
app:layout_constraintEnd_toStartOf="@+id/chip" />
|
|
|
|
<!-- An optional chip view. Used for history. -->
|
|
diff --git a/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/tab-switcher-list-mode.inc b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/tab-switcher-list-mode.inc
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/cromite_flags/chrome/browser/flags/android/chrome_feature_list_cc/tab-switcher-list-mode.inc
|
|
@@ -0,0 +1,3 @@
|
|
+CROMITE_FEATURE(kTabSwitcherListMode,
|
|
+ "TabSwitcherListMode",
|
|
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
|
--
|