Commit 8cfee54c authored by gogerald's avatar gogerald Committed by Commit Bot

[StartSurface] Implement single tab tab switcher

This CL also disables Tab to GTS animation for single start surface,
since it looks bad and does not work for single tab tab switcher.

Screen records:
https://drive.google.com/a/google.com/file/d/1iqIPfeI3GFyfNtz4rHijhlpH4TTbQE_j/view?usp=sharing

TODO: remove 'group tabs' from the menu when showing single tab view.

Binary-Size: Size increase is expected.
Bug: 1065902
Change-Id: I244275e809fefbe4e9106f852fe383201ac1e123
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2125455
Commit-Queue: Ganggui Tang <gogerald@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Reviewed-by: default avatarYue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#755910}
parent 124570ab
......@@ -11,6 +11,7 @@ import org.chromium.base.ApplicationStatus;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tasks.TasksSurface;
import org.chromium.chrome.browser.tasks.TasksSurfaceProperties;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegate.TabSwitcherType;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarConfiguration;
......@@ -207,8 +208,13 @@ public class StartSurfaceCoordinator implements StartSurface {
allProperties.addAll(Arrays.asList(StartSurfaceProperties.ALL_KEYS));
mPropertyModel = new PropertyModel(allProperties);
mTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(mActivity,
mPropertyModel, mSurfaceMode == SurfaceMode.SINGLE_PANE, !excludeMVTiles);
int tabSwitcherType = mSurfaceMode == SurfaceMode.SINGLE_PANE ? TabSwitcherType.CAROUSEL
: TabSwitcherType.GRID;
if (StartSurfaceConfiguration.START_SURFACE_LAST_ACTIVE_TAB_ONLY.getValue()) {
tabSwitcherType = TabSwitcherType.SINGLE;
}
mTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(
mActivity, mPropertyModel, tabSwitcherType, !excludeMVTiles);
mTasksSurface.getView().setId(R.id.primary_tasks_surface_view);
mTasksSurfacePropertyModelChangeProcessor =
......@@ -240,7 +246,7 @@ public class StartSurfaceCoordinator implements StartSurface {
PropertyModel propertyModel = new PropertyModel(TasksSurfaceProperties.ALL_KEYS);
mStartSurfaceMediator.setSecondaryTasksSurfacePropertyModel(propertyModel);
mSecondaryTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(
mActivity, propertyModel, false, false);
mActivity, propertyModel, TabSwitcherType.GRID, false);
mSecondaryTasksSurface.onFinishNativeInitialization(
mActivity, mActivity.getToolbarManager().getFakeboxDelegate());
mSecondaryTasksSurface.initialize();
......
......@@ -26,7 +26,9 @@ public class StartSurfaceConfiguration {
public static final BooleanCachedFieldTrialParameter START_SURFACE_HIDE_INCOGNITO_SWITCH =
new BooleanCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
"hide_switch_when_no_incognito_tabs", false);
public static final BooleanCachedFieldTrialParameter START_SURFACE_LAST_ACTIVE_TAB_ONLY =
new BooleanCachedFieldTrialParameter(
ChromeFeatureList.START_SURFACE_ANDROID, "show_last_active_tab_only", false);
/**
* @return Whether the Start Surface is enabled.
*/
......
......@@ -42,6 +42,7 @@ android_resources("java_resources") {
"java/res/drawable/popup_bg_dark.xml",
"java/res/drawable/selected_tab_background.xml",
"java/res/drawable/selected_tab_background_incognito.xml",
"java/res/drawable/single_tab_background.xml",
"java/res/drawable/tab_grid_dialog_background.xml",
"java/res/drawable/tab_grid_dialog_background_incognito.xml",
"java/res/drawable/tab_grid_selection_list_icon.xml",
......@@ -55,6 +56,7 @@ android_resources("java_resources") {
"java/res/layout/new_tab_tile_card_item.xml",
"java/res/layout/selectable_tab_grid_card_item.xml",
"java/res/layout/selectable_tab_list_card_item.xml",
"java/res/layout/single_tab_view_layout.xml",
"java/res/layout/tab_grid_card_item.xml",
"java/res/layout/tab_grid_dialog_layout.xml",
"java/res/layout/tab_grid_message_card_item.xml",
......@@ -81,6 +83,11 @@ android_library("java") {
sources = [
"java/src/org/chromium/chrome/browser/tasks/MostVisitedListCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherMediator.java",
"java/src/org/chromium/chrome/browser/tasks/SingleTabView.java",
"java/src/org/chromium/chrome/browser/tasks/SingleTabViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/SingleTabViewProperties.java",
"java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediator.java",
"java/src/org/chromium/chrome/browser/tasks/TasksView.java",
......
include_rules = [
"+chrome/browser/android/lifecycle",
"+chrome/browser/profiles/android/java",
"+chrome/browser/ui/android/favicon/java",
"+chrome/browser/ui/messages/android/java",
"+chrome/browser/util",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
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="1dp"
android:color="@color/default_chip_outline_color" />
<corners android:radius="8dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
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.SingleTabView
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:id="@+id/single_tab_view"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/single_tab_background"
android:orientation="horizontal">
<ImageView
android:id="@+id/tab_favicon_view"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/tab_title_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.TextLarge"/>
</org.chromium.chrome.browser.tasks.SingleTabView>
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tasks.tab_management.TabListFaviconProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
/** Coordinator of the single tab tab switcher. */
class SingleTabSwitcherCoordinator implements TabSwitcher {
private final PropertyModelChangeProcessor mPropertyModelChangeProcessor;
private final SingleTabSwitcherMediator mMediator;
private final TabListFaviconProvider mTabListFaviconProvider;
private final TabSwitcher.TabListDelegate mTabListDelegate;
SingleTabSwitcherCoordinator(ChromeActivity activity, ViewGroup container) {
PropertyModel propertyModel = new PropertyModel(SingleTabViewProperties.ALL_KEYS);
SingleTabView singleTabView = (SingleTabView) LayoutInflater.from(activity).inflate(
R.layout.single_tab_view_layout, container, false);
container.addView(singleTabView);
mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
propertyModel, singleTabView, SingleTabViewBinder::bind);
mTabListFaviconProvider = new TabListFaviconProvider(activity);
mMediator = new SingleTabSwitcherMediator(
propertyModel, activity.getTabModelSelector(), mTabListFaviconProvider);
// Most of these interfaces should be unused. They are invalid implementations.
mTabListDelegate = new TabSwitcher.TabListDelegate() {
@Override
public int getResourceId() {
return 0;
}
@Override
public long getLastDirtyTimeForTesting() {
assert false : "should not reach here";
return 0;
}
@Override
@VisibleForTesting
public void setBitmapCallbackForTesting(Callback<Bitmap> callback) {
assert false : "should not reach here";
}
@Override
@VisibleForTesting
public int getBitmapFetchCountForTesting() {
assert false : "should not reach here";
return 0;
}
@Override
@VisibleForTesting
public int getSoftCleanupDelayForTesting() {
assert false : "should not reach here";
return 0;
}
@Override
@VisibleForTesting
public int getCleanupDelayForTesting() {
assert false : "should not reach here";
return 0;
}
@Override
@VisibleForTesting
public int getTabListTopOffset() {
return 0;
}
@Override
@VisibleForTesting
public int getListModeForTesting() {
assert false : "should not reach here";
return 0;
}
@Override
public boolean prepareOverview() {
return true;
}
@Override
public void postHiding() {}
@Override
public Rect getThumbnailLocationOfCurrentTab(boolean forceUpdate) {
assert false : "should not reach here";
return null;
}
};
}
// TabSwitcher implementation.
@Override
public void setOnTabSelectingListener(OnTabSelectingListener listener) {
mMediator.setOnTabSelectingListener(listener);
}
@Override
public void initWithNative(Context context, TabContentManager tabContentManager,
DynamicResourceLoader dynamicResourceLoader,
SnackbarManager.SnackbarManageable snackbarManageable) {
mTabListFaviconProvider.initWithNative(Profile.getLastUsedRegularProfile());
}
@Override
public Controller getController() {
return mMediator;
}
@Override
public TabListDelegate getTabListDelegate() {
return mTabListDelegate;
}
@Override
public TabDialogDelegation getTabGridDialogDelegation() {
assert false : "should not reach here";
return null;
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.FAVICON;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.TITLE;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.chromium.base.ObserverList;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabList;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tasks.tab_management.TabListFaviconProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.ui.modelutil.PropertyModel;
/** Mediator of the single tab tab switcher. */
class SingleTabSwitcherMediator implements TabSwitcher.Controller {
private final ObserverList<TabSwitcher.OverviewModeObserver> mObservers = new ObserverList<>();
private final TabModelSelector mTabModelSelector;
private final PropertyModel mPropertyModel;
private final TabModel mNormalTabModel;
private final TabListFaviconProvider mTabListFaviconProvider;
private final TabModelObserver mNormalTabModelObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private TabSwitcher.OnTabSelectingListener mTabSelectingListener;
private boolean mShouldIgnoreNextSelect;
SingleTabSwitcherMediator(PropertyModel propertyModel, TabModelSelector tabModelSelector,
TabListFaviconProvider tabListFaviconProvider) {
mTabModelSelector = tabModelSelector;
mPropertyModel = propertyModel;
mNormalTabModel = mTabModelSelector.getModel(false);
mTabListFaviconProvider = tabListFaviconProvider;
mPropertyModel.set(FAVICON, mTabListFaviconProvider.getDefaultFaviconDrawable(false));
mPropertyModel.set(CLICK_LISTENER, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mTabSelectingListener != null
&& mNormalTabModel.index() != TabList.INVALID_TAB_INDEX) {
mTabSelectingListener.onTabSelecting(LayoutManager.time(),
mNormalTabModel.getTabAt(mNormalTabModel.index()).getId());
}
}
});
mNormalTabModelObserver = new EmptyTabModelObserver() {
@Override
public void didSelectTab(Tab tab, int type, int lastId) {
assert overviewVisible();
if (mShouldIgnoreNextSelect) {
mShouldIgnoreNextSelect = false;
return;
}
mTabSelectingListener.onTabSelecting(LayoutManager.time(), tab.getId());
}
};
mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() {
@Override
public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
if (!newModel.isIncognito()) mShouldIgnoreNextSelect = true;
}
};
}
void setOnTabSelectingListener(TabSwitcher.OnTabSelectingListener listener) {
mTabSelectingListener = listener;
}
// Controller implementation
@Override
public boolean overviewVisible() {
return mPropertyModel.get(IS_VISIBLE);
}
@Override
public void addOverviewModeObserver(TabSwitcher.OverviewModeObserver observer) {
mObservers.addObserver(observer);
}
@Override
public void removeOverviewModeObserver(TabSwitcher.OverviewModeObserver observer) {
mObservers.removeObserver(observer);
}
@Override
public void hideOverview(boolean animate) {
mShouldIgnoreNextSelect = false;
mNormalTabModel.removeObserver(mNormalTabModelObserver);
mTabModelSelector.removeObserver(mTabModelSelectorObserver);
mPropertyModel.set(IS_VISIBLE, false);
mPropertyModel.set(FAVICON, mTabListFaviconProvider.getDefaultFaviconDrawable(false));
mPropertyModel.set(TITLE, "");
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.startedHiding();
}
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.finishedHiding();
}
}
@Override
public void showOverview(boolean animate) {
mNormalTabModel.addObserver(mNormalTabModelObserver);
mTabModelSelector.addObserver(mTabModelSelectorObserver);
int selectedTabIndex = mNormalTabModel.index();
if (selectedTabIndex != TabList.INVALID_TAB_INDEX) {
assert mNormalTabModel.getCount() > 0;
Tab tab = mNormalTabModel.getTabAt(selectedTabIndex);
mPropertyModel.set(TITLE, tab.getTitle());
mTabListFaviconProvider.getFaviconForUrlAsync(tab.getUrlString(), false,
(Drawable favicon) -> { mPropertyModel.set(FAVICON, favicon); });
}
mPropertyModel.set(IS_VISIBLE, true);
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.startedShowing();
}
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.finishedShowing();
}
}
@Override
public boolean onBackPressed() {
return false;
}
@Override
public void enableRecordingFirstMeaningfulPaint(long activityCreateTimeMs) {}
}
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.chromium.chrome.tab_ui.R;
/** View of the tab on the single tab tab switcher. */
class SingleTabView extends LinearLayout {
private final Context mContext;
private ImageView mFavicon;
private TextView mTitle;
/** Default constructor needed to inflate via XML. */
public SingleTabView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mFavicon = findViewById(R.id.tab_favicon_view);
mTitle = findViewById(R.id.tab_title_view);
}
/**
* Set the favicon.
* @param favicon The given favicon {@link Drawable}.
*/
public void setFavicon(Drawable favicon) {
mFavicon.setImageDrawable(favicon);
}
/**
* Set the title.
* @param title The given title.
*/
public void setTitle(String title) {
mTitle.setText(title);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.FAVICON;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.TITLE;
import android.view.View;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
// The view binder of the single tab view.
class SingleTabViewBinder {
public static void bind(PropertyModel model, SingleTabView view, PropertyKey propertyKey) {
if (propertyKey == CLICK_LISTENER) {
view.setOnClickListener(model.get(CLICK_LISTENER));
} else if (propertyKey == FAVICON) {
view.setFavicon(model.get(FAVICON));
} else if (propertyKey == IS_VISIBLE) {
view.setVisibility(model.get(IS_VISIBLE) ? View.VISIBLE : View.GONE);
} else if (propertyKey == TITLE) {
view.setTitle(model.get(TITLE));
} else {
assert false : "Unsupported property key";
}
}
}
\ No newline at end of file
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import android.graphics.drawable.Drawable;
import android.view.View;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/** List of the single tab view properties. */
class SingleTabViewProperties {
private SingleTabViewProperties() {}
public static final PropertyModel
.WritableObjectPropertyKey<View.OnClickListener> CLICK_LISTENER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<Drawable> FAVICON =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableBooleanPropertyKey IS_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey();
public static final PropertyModel.WritableObjectPropertyKey<String> TITLE =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {CLICK_LISTENER, FAVICON, IS_VISIBLE, TITLE};
}
......@@ -15,9 +15,9 @@ import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.ntp.FakeboxDelegate;
import org.chromium.chrome.browser.ntp.IncognitoCookieControlsManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegate.TabSwitcherType;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementModuleProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherCoordinator;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
......@@ -35,18 +35,24 @@ public class TasksSurfaceCoordinator implements TasksSurface {
private final PropertyModel mPropertyModel;
public TasksSurfaceCoordinator(ChromeActivity activity, PropertyModel propertyModel,
boolean isTabCarousel, boolean hasMVTiles) {
@TabSwitcherType int tabSwitcherType, boolean hasMVTiles) {
mView = (TasksView) LayoutInflater.from(activity).inflate(R.layout.tasks_view_layout, null);
mView.initialize(activity.getLifecycleDispatcher());
mPropertyModelChangeProcessor =
PropertyModelChangeProcessor.create(propertyModel, mView, TasksViewBinder::bind);
mPropertyModel = propertyModel;
if (isTabCarousel) {
if (tabSwitcherType == TabSwitcherType.CAROUSEL) {
mTabSwitcher = TabManagementModuleProvider.getDelegate().createCarouselTabSwitcher(
activity, mView.getCarouselTabSwitcherContainer());
} else {
} else if (tabSwitcherType == TabSwitcherType.GRID) {
mTabSwitcher = TabManagementModuleProvider.getDelegate().createGridTabSwitcher(
activity, mView.getBodyViewContainer());
} else if (tabSwitcherType == TabSwitcherType.SINGLE) {
mTabSwitcher = new SingleTabSwitcherCoordinator(
activity, mView.getCarouselTabSwitcherContainer());
} else {
mTabSwitcher = null;
assert false : "Unsupported tab switcher type";
}
View.OnClickListener incognitoLearnMoreClickListener = v -> {
......@@ -54,12 +60,10 @@ public class TasksSurfaceCoordinator implements TasksSurface {
activity.getString(R.string.help_context_incognito_learn_more),
Profile.getLastUsedRegularProfile().getOffTheRecordProfile(), null);
};
IncognitoCookieControlsManager incognitoCookieControlsManager =
new IncognitoCookieControlsManager();
mMediator = new TasksSurfaceMediator(propertyModel, incognitoLearnMoreClickListener,
incognitoCookieControlsManager, isTabCarousel);
incognitoCookieControlsManager, tabSwitcherType == TabSwitcherType.CAROUSEL);
if (hasMVTiles) {
LinearLayout mvTilesLayout = mView.findViewById(R.id.mv_tiles_layout);
......@@ -103,9 +107,8 @@ public class TasksSurfaceCoordinator implements TasksSurface {
@Override
public void onFinishNativeInitialization(Context context, FakeboxDelegate fakeboxDelegate) {
ChromeActivity activity = (ChromeActivity) context;
((TabSwitcherCoordinator) mTabSwitcher)
.initWithNative(activity, activity.getTabContentManager(),
activity.getCompositorViewHolder().getDynamicResourceLoader(), activity);
mTabSwitcher.initWithNative(activity, activity.getTabContentManager(),
activity.getCompositorViewHolder().getDynamicResourceLoader(), activity);
mMediator.initWithNative(fakeboxDelegate);
}
......
......@@ -8,6 +8,8 @@ import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ThemeColorProvider;
......@@ -23,6 +25,9 @@ import org.chromium.chrome.features.start_surface.StartSurface;
import org.chromium.components.module_installer.builder.ModuleInterface;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Interface to get access to components concerning tab management.
* TODO(crbug.com/982018): Move DFM configurations to 'chrome/android/modules/start_surface/'
......@@ -30,17 +35,25 @@ import org.chromium.ui.modelutil.PropertyModel;
@ModuleInterface(module = "tab_management",
impl = "org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegateImpl")
public interface TabManagementDelegate {
@IntDef({TabSwitcherType.GRID, TabSwitcherType.CAROUSEL, TabSwitcherType.SINGLE})
@Retention(RetentionPolicy.SOURCE)
public @interface TabSwitcherType {
int GRID = 0;
int CAROUSEL = 1;
int SINGLE = 2;
}
/**
* Create the {@link TasksSurface}
* @param activity The {@link ChromeActivity} that creates this surface.
* @param propertyModel The {@link PropertyModel} contains the {@link TasksSurfaceProperties} to
* communicate with this surface.
* @param isTabCarousel Whether show the Tabs in carousel mode.
* @param tabSwitcherType The type of the tab switcher to show.
* @param hasMVTiles whether has MV tiles on the surface.
* @return The {@link TasksSurface}.
*/
TasksSurface createTasksSurface(ChromeActivity activity, PropertyModel propertyModel,
boolean isTabCarousel, boolean hasMVTiles);
@TabSwitcherType int tabSwitcherType, boolean hasMVTiles);
/**
* Create the {@link TabSwitcher} to display Tabs in grid.
......
......@@ -38,8 +38,8 @@ import org.chromium.ui.modelutil.PropertyModel;
public class TabManagementDelegateImpl implements TabManagementDelegate {
@Override
public TasksSurface createTasksSurface(ChromeActivity activity, PropertyModel propertyModel,
boolean isTabCarousel, boolean hasMVTiles) {
return new TasksSurfaceCoordinator(activity, propertyModel, isTabCarousel, hasMVTiles);
@TabSwitcherType int tabSwitcherType, boolean hasMVTiles) {
return new TasksSurfaceCoordinator(activity, propertyModel, tabSwitcherType, hasMVTiles);
}
@Override
......
......@@ -164,6 +164,7 @@ public class TabUiFeatureUtilities {
Log.d(TAG, "GTS.MinMemoryMB = " + ZOOMING_MIN_MEMORY.getValue());
return CachedFeatureFlags.isEnabled(ChromeFeatureList.TAB_TO_GTS_ANIMATION)
&& Build.VERSION.SDK_INT >= ZOOMING_MIN_SDK.getValue()
&& SysUtils.amountOfPhysicalMemoryKB() / 1024 >= ZOOMING_MIN_MEMORY.getValue();
&& SysUtils.amountOfPhysicalMemoryKB() / 1024 >= ZOOMING_MIN_MEMORY.getValue()
&& !StartSurfaceConfiguration.isStartSurfaceSinglePaneEnabled();
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.Mockito.verify;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.FAVICON;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.TITLE;
import android.graphics.drawable.BitmapDrawable;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.chrome.tab_ui.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.DummyUiActivityTestCase;
/** Tests for {@link SingleTabViewBinder}. */
@RunWith(ChromeJUnit4ClassRunner.class)
public class SingleTabViewBinderTest extends DummyUiActivityTestCase {
private SingleTabView mSingleTabView;
private PropertyModelChangeProcessor mPropertyModelChangeProcessor;
private PropertyModel mPropertyModel;
private String mTitle = "test";
@Mock
private View.OnClickListener mClickListener;
@Override
public void setUpTest() throws Exception {
super.setUpTest();
MockitoAnnotations.initMocks(this);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mSingleTabView = (SingleTabView) getActivity().getLayoutInflater().inflate(
R.layout.single_tab_view_layout, null);
getActivity().setContentView(mSingleTabView);
mPropertyModel = new PropertyModel(SingleTabViewProperties.ALL_KEYS);
mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
mPropertyModel, mSingleTabView, SingleTabViewBinder::bind);
});
}
@Override
public void tearDownTest() throws Exception {
mPropertyModelChangeProcessor.destroy();
mPropertyModel = null;
mSingleTabView = null;
}
private boolean isViewVisible(int viewId) {
return mSingleTabView.findViewById(viewId).getVisibility() == View.VISIBLE;
}
@Test
@UiThreadTest
@SmallTest
public void testSetTitle() {
mPropertyModel.set(IS_VISIBLE, true);
assertTrue(isViewVisible(R.id.single_tab_view));
TextView title = mSingleTabView.findViewById(R.id.tab_title_view);
assertEquals("", title.getText());
mPropertyModel.set(TITLE, mTitle);
assertEquals(mTitle, title.getText());
mPropertyModel.set(IS_VISIBLE, false);
assertFalse(isViewVisible(R.id.single_tab_view));
}
@Test
@UiThreadTest
@SmallTest
public void testSetFavicon() {
mPropertyModel.set(IS_VISIBLE, true);
assertTrue(isViewVisible(R.id.single_tab_view));
ImageView favicon = mSingleTabView.findViewById(R.id.tab_favicon_view);
assertNull(favicon.getDrawable());
mPropertyModel.set(FAVICON, new BitmapDrawable());
assertNotNull(favicon.getDrawable());
mPropertyModel.set(IS_VISIBLE, false);
assertFalse(isViewVisible(R.id.single_tab_view));
}
@Test
@UiThreadTest
@SmallTest
public void testClickListener() {
mPropertyModel.set(IS_VISIBLE, true);
assertTrue(isViewVisible(R.id.single_tab_view));
mPropertyModel.set(CLICK_LISTENER, mClickListener);
mSingleTabView.performClick();
verify(mClickListener).onClick(anyObject());
mPropertyModel.set(IS_VISIBLE, false);
assertFalse(isViewVisible(R.id.single_tab_view));
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.FAVICON;
import static org.chromium.chrome.browser.tasks.SingleTabViewProperties.TITLE;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_management.TabListFaviconProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.ui.modelutil.PropertyModel;
/** Tests for {@link SingleTabSwitcherMediator}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SingleTabSwitcherMediatorUnitTest {
private final int mTabId = 1;
private final String mTitle = "test";
private final String mUrlString = "chrome://test.com";
private SingleTabSwitcherMediator mMediator;
private PropertyModel mPropertyModel;
@Mock
private TabModelSelector mTabModelSelector;
@Mock
private TabModel mNormalTabModel;
@Mock
private TabModel mIncognitoTabModel;
@Mock
private Tab mTab;
@Mock
private TabListFaviconProvider mTabListFaviconProvider;
@Mock
private TabSwitcher.OnTabSelectingListener mOnTabSelectingListener;
@Mock
private TabSwitcher.OverviewModeObserver mOverviewModeObserver;
@Captor
private ArgumentCaptor<EmptyTabModelSelectorObserver> mTabModelSelectorObserverCaptor;
@Captor
private ArgumentCaptor<EmptyTabModelObserver> mTabModelObserverCaptor;
@Captor
private ArgumentCaptor<Callback<Drawable>> mFaviconCallbackCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(new BitmapDrawable())
.when(mTabListFaviconProvider)
.getDefaultFaviconDrawable(false);
doReturn(mNormalTabModel).when(mTabModelSelector).getModel(false);
doReturn(mTab).when(mNormalTabModel).getTabAt(0);
doReturn(0).when(mNormalTabModel).index();
doReturn(1).when(mNormalTabModel).getCount();
doReturn(false).when(mNormalTabModel).isIncognito();
doReturn(mUrlString).when(mTab).getUrlString();
doReturn(mTabId).when(mTab).getId();
doReturn(mTitle).when(mTab).getTitle();
doReturn(true).when(mIncognitoTabModel).isIncognito();
mPropertyModel = new PropertyModel(SingleTabViewProperties.ALL_KEYS);
mMediator = new SingleTabSwitcherMediator(
mPropertyModel, mTabModelSelector, mTabListFaviconProvider);
}
@After
public void tearDown() {
mMediator = null;
}
@Test
public void showAndHide() {
assertNotNull(mPropertyModel.get(FAVICON));
assertNotNull(mPropertyModel.get(CLICK_LISTENER));
assertFalse(mMediator.overviewVisible());
mMediator.setOnTabSelectingListener(mOnTabSelectingListener);
mMediator.addOverviewModeObserver(mOverviewModeObserver);
mMediator.showOverview(true);
verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
verify(mTabListFaviconProvider)
.getFaviconForUrlAsync(eq(mUrlString), eq(false), mFaviconCallbackCaptor.capture());
assertTrue(mMediator.overviewVisible());
verify(mOverviewModeObserver).startedShowing();
verify(mOverviewModeObserver).finishedShowing();
mPropertyModel.get(CLICK_LISTENER).onClick(null);
verify(mOnTabSelectingListener).onTabSelecting(anyLong(), eq(mTabId));
mMediator.hideOverview(true);
assertFalse(mMediator.overviewVisible());
assertEquals(mPropertyModel.get(TITLE), "");
verify(mOverviewModeObserver).startedHiding();
verify(mOverviewModeObserver).finishedHiding();
mMediator.removeOverviewModeObserver(mOverviewModeObserver);
mMediator.setOnTabSelectingListener(null);
}
@Test
public void selectTabAfterSwitchingTabModel() {
assertFalse(mMediator.overviewVisible());
mMediator.setOnTabSelectingListener(mOnTabSelectingListener);
mMediator.addOverviewModeObserver(mOverviewModeObserver);
mMediator.showOverview(true);
verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
verify(mTabListFaviconProvider)
.getFaviconForUrlAsync(eq(mUrlString), eq(false), mFaviconCallbackCaptor.capture());
assertTrue(mMediator.overviewVisible());
verify(mOverviewModeObserver).startedShowing();
verify(mOverviewModeObserver).finishedShowing();
mTabModelObserverCaptor.getValue().didSelectTab(mTab, TabSelectionType.FROM_USER, -1);
verify(mOnTabSelectingListener).onTabSelecting(anyLong(), eq(mTabId));
mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
mIncognitoTabModel, mNormalTabModel);
mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
mNormalTabModel, mIncognitoTabModel);
// The next tab selecting event should be ignored.
mTabModelObserverCaptor.getValue().didSelectTab(mTab, TabSelectionType.FROM_USER, mTabId);
verify(mOnTabSelectingListener, times(1)).onTabSelecting(anyLong(), eq(mTabId));
mTabModelObserverCaptor.getValue().didSelectTab(mTab, TabSelectionType.FROM_USER, mTabId);
verify(mOnTabSelectingListener, times(2)).onTabSelecting(anyLong(), eq(mTabId));
mMediator.hideOverview(true);
assertFalse(mMediator.overviewVisible());
assertEquals(mPropertyModel.get(TITLE), "");
verify(mOverviewModeObserver).startedHiding();
verify(mOverviewModeObserver).finishedHiding();
}
@Test
public void selectTabAfterSwitchingTabModelAndReshown() {
assertFalse(mMediator.overviewVisible());
mMediator.setOnTabSelectingListener(mOnTabSelectingListener);
mMediator.addOverviewModeObserver(mOverviewModeObserver);
mMediator.showOverview(true);
verify(mNormalTabModel).addObserver(mTabModelObserverCaptor.capture());
verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
verify(mTabListFaviconProvider)
.getFaviconForUrlAsync(eq(mUrlString), eq(false), mFaviconCallbackCaptor.capture());
mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
mIncognitoTabModel, mNormalTabModel);
mTabModelSelectorObserverCaptor.getValue().onTabModelSelected(
mNormalTabModel, mIncognitoTabModel);
mMediator.hideOverview(true);
// The next tab selecting event should not be ignored after hiding and reshowing.
mMediator.showOverview(true);
mTabModelObserverCaptor.getValue().didSelectTab(mTab, TabSelectionType.FROM_USER, -1);
verify(mOnTabSelectingListener).onTabSelecting(anyLong(), eq(mTabId));
mMediator.hideOverview(true);
}
}
......@@ -27,6 +27,7 @@ public_tab_management_java_sources = [
public_tab_management_java_sources += start_surface_public_java_sources
tab_management_test_java_sources = [
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/SingleTabViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/TasksViewBinderTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/AssertsTest.java",
"//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/MessageCardProviderTest.java",
......@@ -54,6 +55,7 @@ tab_management_test_java_sources = [
tab_management_junit_java_sources = [
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/SingleTabSwitcherMediatorUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/TasksSurfaceMediatorUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java",
......
......@@ -70,6 +70,7 @@ public class ChromeCachedFlags {
BottomToolbarVariationManager.BOTTOM_TOOLBAR_VARIATION,
StartSurfaceConfiguration.START_SURFACE_EXCLUDE_MV_TILES,
StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH,
StartSurfaceConfiguration.START_SURFACE_LAST_ACTIVE_TAB_ONLY,
StartSurfaceConfiguration.START_SURFACE_VARIATION,
TabContentManager.ALLOW_TO_REFETCH_TAB_THUMBNAIL_VARIATION,
TabUiFeatureUtilities.ENABLE_SEARCH_CHIP,
......
......@@ -1405,6 +1405,11 @@ const FeatureEntry::FeatureParam
{"exclude_mv_tiles", "true"},
{"hide_switch_when_no_incognito_tabs", "true"}};
const FeatureEntry::FeatureParam kStartSurfaceAndroid_SingleSurfaceSingleTab[] =
{{"start_surface_variation", "single"},
{"show_last_active_tab_only", "true"},
{"hide_switch_when_no_incognito_tabs", "true"}};
const FeatureEntry::FeatureParam kStartSurfaceAndroid_TwoPanesSurface[] = {
{"start_surface_variation", "twopanes"}};
......@@ -1421,6 +1426,8 @@ const FeatureEntry::FeatureVariation kStartSurfaceAndroidVariations[] = {
{"Single Surface without MV Tiles",
kStartSurfaceAndroid_SingleSurfaceWithoutMvTiles,
base::size(kStartSurfaceAndroid_SingleSurfaceWithoutMvTiles), nullptr},
{"Single Surface Single Tab", kStartSurfaceAndroid_SingleSurfaceSingleTab,
base::size(kStartSurfaceAndroid_SingleSurfaceSingleTab), nullptr},
{"Two Panes Surface", kStartSurfaceAndroid_TwoPanesSurface,
base::size(kStartSurfaceAndroid_TwoPanesSurface), nullptr},
{"Tasks Only", kStartSurfaceAndroid_TasksOnly,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment