blob: 5d33e8c4341f2d37d12e79a4a4925e249952bff2 [file] [log] [blame]
// Copyright 2019 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.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.ANIMATE_VISIBILITY_CHANGES;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.BOTTOM_CONTROLS_HEIGHT;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.INITIAL_SCROLL_INDEX;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_INCOGNITO;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.IS_VISIBLE;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.SHADOW_TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.TOP_CONTROLS_HEIGHT;
import static org.chromium.chrome.browser.tasks.tab_management.TabListContainerProperties.VISIBILITY_LISTENER;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.SystemClock;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.init.FirstDrawDetector;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabFeatureUtilities;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabList;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
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.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabSelectionType;
import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.tab_ui.R;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.modelutil.PropertyModel;
import java.util.List;
/**
* The Mediator that is responsible for resetting the tab grid or carousel based on visibility and
* model changes.
*/
class TabSwitcherMediator implements TabSwitcher.Controller, TabListRecyclerView.VisibilityListener,
TabListMediator.GridCardOnClickListenerProvider {
private static final String TAG = "TabSwitcherMediator";
// This should be the same as TabListCoordinator.GRID_LAYOUT_SPAN_COUNT for the selected tab
// to be on the 2nd row.
static final int INITIAL_SCROLL_INDEX_OFFSET = 2;
private static final int DEFAULT_TOP_PADDING = 0;
// Count histograms for tab counts when showing switcher.
static final String TAB_COUNT_HISTOGRAM = "Tabs.TabCountInSwitcher";
static final String TAB_ENTRIES_HISTOGRAM = "Tabs.IndependentTabCountInSwitcher";
/** Field trial parameter for the {@link TabListRecyclerView} cleanup delay. */
private static final String SOFT_CLEANUP_DELAY_PARAM = "soft-cleanup-delay";
private static final int DEFAULT_SOFT_CLEANUP_DELAY_MS = 3_000;
private static final String CLEANUP_DELAY_PARAM = "cleanup-delay";
private static final int DEFAULT_CLEANUP_DELAY_MS = 30_000;
private Integer mSoftCleanupDelayMsForTesting;
private Integer mCleanupDelayMsForTesting;
private final Handler mHandler;
private final Runnable mSoftClearTabListRunnable;
private final Runnable mClearTabListRunnable;
private final ResetHandler mResetHandler;
private final PropertyModel mContainerViewModel;
private final TabModelSelector mTabModelSelector;
private final TabModelObserver mTabModelObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private final ObserverList<TabSwitcher.OverviewModeObserver> mObservers = new ObserverList<>();
private final ChromeFullscreenManager mFullscreenManager;
private TabGridDialogMediator.DialogController mTabGridDialogController;
private final ChromeFullscreenManager.FullscreenListener mFullscreenListener =
new ChromeFullscreenManager.FullscreenListener() {
@Override
public void onContentOffsetChanged(int offset) {}
@Override
public void onControlsOffsetChanged(
int topOffset, int bottomOffset, boolean needsAnimate) {}
@Override
public void onToggleOverlayVideoMode(boolean enabled) {}
@Override
public void onBottomControlsHeightChanged(int bottomControlsHeight) {
mContainerViewModel.set(BOTTOM_CONTROLS_HEIGHT, bottomControlsHeight);
}
};
private final ViewGroup mContainerView;
private final TabSelectionEditorCoordinator
.TabSelectionEditorController mTabSelectionEditorController;
private final TabContentManager mTabContentManager;
private TabSwitcher.OnTabSelectingListener mOnTabSelectingListener;
private IphProvider mIphProvider;
/**
* In cases where a didSelectTab was due to switching models with a toggle,
* we don't change tab grid visibility.
*/
private boolean mShouldIgnoreNextSelect;
private int mModelIndexWhenShown;
private int mTabIdWhenShown;
private int mIndexInNewModelWhenSwitched;
private boolean mIsSelectingInTabSwitcher;
private boolean mShowTabsInMruOrder;
private static class FirstMeaningfulPaintRecorder {
private final long mActivityCreationTimeMs;
private FirstMeaningfulPaintRecorder(long activityCreationTimeMs) {
mActivityCreationTimeMs = activityCreationTimeMs;
}
private void record(int numOfThumbnails) {
assert numOfThumbnails >= 0;
long elasped = SystemClock.elapsedRealtime() - mActivityCreationTimeMs;
PostTask.postTask(TaskTraits.CURRENT_THREAD_BEST_EFFORT,
()
-> ReturnToChromeExperimentsUtil.recordTimeToGTSFirstMeaningfulPaint(
elasped, numOfThumbnails));
}
}
private FirstMeaningfulPaintRecorder mFirstMeaningfulPaintRecorder;
private boolean mRegisteredFirstMeaningfulPaintRecorder;
/**
* Interface to delegate resetting the tab grid.
*/
interface ResetHandler {
/**
* Reset the tab grid with the given {@link TabList}, which can be null.
* @param tabList The {@link TabList} to show the tabs for in the grid.
* @param quickMode Whether to skip capturing the selected live tab for the thumbnail.
* @param mruMode Whether order the Tabs by MRU.
* @return Whether the {@link TabListRecyclerView} can be shown quickly.
*/
boolean resetWithTabList(@Nullable TabList tabList, boolean quickMode, boolean mruMode);
/**
* Release the thumbnail {@link Bitmap} but keep the {@link TabGridView}.
*/
void softCleanup();
}
/**
* An interface to control whether to show IPH on grid tab switcher.
*/
public interface IphProvider {
/**
* Shows IPH on grid tab switcher based on incognito mode.
* @param isIncognito Whether IPH is shown for incognito mode.
*/
void maybeShowIPH(boolean isIncognito);
}
/**
* Basic constructor for the Mediator.
* @param resetHandler The {@link ResetHandler} that handles reset for this Mediator.
* @param containerViewModel The {@link PropertyModel} to keep state on the View containing the
* grid or carousel.
* @param tabModelSelector {@link TabModelSelector} to observer for model and selection changes.
* @param fullscreenManager {@link FullscreenManager} to use.
* @param containerView The container {@link ViewGroup} to use.
* @param tabSelectionEditorController The controller that can control the visibility of the
* TabSelectionEditor.
* @param tabContentManager The {@link TabContentManager} for first meaningful paint event.
* @param mode One of the {@link TabListCoordinator.TabListMode}.
*/
TabSwitcherMediator(ResetHandler resetHandler, PropertyModel containerViewModel,
TabModelSelector tabModelSelector, ChromeFullscreenManager fullscreenManager,
ViewGroup containerView,
TabSelectionEditorCoordinator.TabSelectionEditorController tabSelectionEditorController,
TabContentManager tabContentManager, @TabListCoordinator.TabListMode int mode) {
mResetHandler = resetHandler;
mContainerViewModel = containerViewModel;
mTabModelSelector = tabModelSelector;
mFullscreenManager = fullscreenManager;
mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() {
@Override
public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
mShouldIgnoreNextSelect = true;
mIndexInNewModelWhenSwitched = newModel.index();
TabList currentTabModelFilter =
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter();
mContainerViewModel.set(IS_INCOGNITO, currentTabModelFilter.isIncognito());
if (mTabGridDialogController != null) {
mTabGridDialogController.hideDialog(false);
}
if (!mContainerViewModel.get(IS_VISIBLE)) return;
mResetHandler.resetWithTabList(currentTabModelFilter, false, mShowTabsInMruOrder);
if (mIphProvider != null) {
mIphProvider.maybeShowIPH(mTabModelSelector.isIncognitoSelected());
}
}
};
mTabModelSelector.addObserver(mTabModelSelectorObserver);
mTabModelObserver = new EmptyTabModelObserver() {
@Override
public void didAddTab(Tab tab, int type) {
mShouldIgnoreNextSelect = false;
}
@Override
public void didSelectTab(Tab tab, int type, int lastId) {
if (type == TabSelectionType.FROM_CLOSE || mShouldIgnoreNextSelect) {
mShouldIgnoreNextSelect = false;
return;
}
if (mIsSelectingInTabSwitcher) {
mIsSelectingInTabSwitcher = false;
TabModelFilter modelFilter = mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter();
if (modelFilter instanceof TabGroupModelFilter) {
((TabGroupModelFilter) modelFilter).recordSessionsCount(tab);
}
// Use TabSelectionType.From_USER to filter the new tab creation case.
if (type == TabSelectionType.FROM_USER) recordUserSwitchedTab(tab, lastId);
}
if (mContainerViewModel.get(IS_VISIBLE)) {
onTabSelecting(tab.getId());
}
}
@Override
public void restoreCompleted() {
if (!mContainerViewModel.get(IS_VISIBLE)) return;
mResetHandler.resetWithTabList(
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(),
false, mShowTabsInMruOrder);
setInitialScrollIndexOffset();
}
};
mFullscreenManager.addListener(mFullscreenListener);
mTabModelSelector.getTabModelFilterProvider().addTabModelFilterObserver(mTabModelObserver);
mContainerViewModel.set(VISIBILITY_LISTENER, this);
mContainerViewModel.set(IS_INCOGNITO,
mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.isIncognito());
mContainerViewModel.set(ANIMATE_VISIBILITY_CHANGES, true);
// Container view takes care of padding and margin in start surface.
if (mode != TabListCoordinator.TabListMode.CAROUSEL) {
// The start surface checks in this block are for top controls height and shadow margin
// to be set correctly for displaying the omnibox above the tab switcher.
int topControlsHeight = FeatureUtilities.isStartSurfaceEnabled()
? 0
: fullscreenManager.getTopControlsHeight();
mContainerViewModel.set(TOP_CONTROLS_HEIGHT, topControlsHeight);
mContainerViewModel.set(
BOTTOM_CONTROLS_HEIGHT, fullscreenManager.getBottomControlsHeight());
int toolbarHeight =
ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
R.dimen.toolbar_height_no_shadow);
mContainerViewModel.set(SHADOW_TOP_MARGIN,
FeatureUtilities.isStartSurfaceEnabled() ? 0 : toolbarHeight);
}
mContainerView = containerView;
mSoftClearTabListRunnable = mResetHandler::softCleanup;
mClearTabListRunnable =
() -> mResetHandler.resetWithTabList(null, false, mShowTabsInMruOrder);
mHandler = new Handler();
mTabSelectionEditorController = tabSelectionEditorController;
mTabContentManager = tabContentManager;
// TODO(crbug.com/982018): Let the start surface pass in the parameter and add unit test for
// it. This is a temporary solution to keep this change minimum.
mShowTabsInMruOrder = isShowingTabsInMRUOrder();
}
/**
* Set the controller of the TabGridDialog so that it can be directly controlled.
* @param tabGridDialogController The handler of the Grid Dialog
*/
void setTabGridDialogController(
TabGridDialogMediator.DialogController tabGridDialogController) {
mTabGridDialogController = tabGridDialogController;
}
@VisibleForTesting
int getSoftCleanupDelayForTesting() {
return getSoftCleanupDelay();
}
private int getSoftCleanupDelay() {
if (mSoftCleanupDelayMsForTesting != null) return mSoftCleanupDelayMsForTesting;
String delay = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, SOFT_CLEANUP_DELAY_PARAM);
try {
return Integer.valueOf(delay);
} catch (NumberFormatException e) {
return DEFAULT_SOFT_CLEANUP_DELAY_MS;
}
}
@VisibleForTesting
int getCleanupDelayForTesting() {
return getCleanupDelay();
}
private int getCleanupDelay() {
if (mCleanupDelayMsForTesting != null) return mCleanupDelayMsForTesting;
String delay = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, CLEANUP_DELAY_PARAM);
try {
return Integer.valueOf(delay);
} catch (NumberFormatException e) {
return DEFAULT_CLEANUP_DELAY_MS;
}
}
private void setVisibility(boolean isVisible) {
if (isVisible) {
RecordUserAction.record("MobileToolbarShowStackView");
}
mContainerViewModel.set(IS_VISIBLE, isVisible);
}
/**
* Record tab switch related metric for GTS.
* @param tab The new selected tab.
* @param lastId The id of the previous selected tab, and that tab is still a valid tab
* in TabModel.
*/
private void recordUserSwitchedTab(Tab tab, int lastId) {
if (tab == null) {
assert false : "New selected tab cannot be null when recording tab switch.";
return;
}
Tab fromTab = TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), lastId);
assert fromTab != null;
if (mModelIndexWhenShown == mTabModelSelector.getCurrentModelIndex()) {
if (tab.getId() == mTabIdWhenShown) {
RecordUserAction.record("MobileTabReturnedToCurrentTab");
RecordHistogram.recordSparseHistogram(
"Tabs.TabOffsetOfSwitch." + TabSwitcherCoordinator.COMPONENT_NAME, 0);
} else {
int fromIndex = mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.indexOf(fromTab);
int toIndex = mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.indexOf(tab);
if (fromIndex != toIndex || fromTab.getId() == tab.getId()) {
// Only log when you switch a tab page directly from tab switcher.
if (!FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()
|| getRelatedTabs(tab.getId()).size() == 1) {
RecordUserAction.record(
"MobileTabSwitched." + TabSwitcherCoordinator.COMPONENT_NAME);
}
RecordHistogram.recordSparseHistogram(
"Tabs.TabOffsetOfSwitch." + TabSwitcherCoordinator.COMPONENT_NAME,
fromIndex - toIndex);
}
}
} else {
int newSelectedTabIndex =
TabModelUtils.getTabIndexById(mTabModelSelector.getCurrentModel(), tab.getId());
if (newSelectedTabIndex == mIndexInNewModelWhenSwitched) {
// TabModelImpl logs this action only when a different index is set within a
// TabModelImpl. If we switch between normal tab model and incognito tab model and
// leave the index the same (i.e. after switched tab model and select the
// highlighted tab), TabModelImpl doesn't catch this case. Therefore, we record it
// here.
RecordUserAction.record("MobileTabSwitched");
}
// Only log when you switch a tab page directly from tab switcher.
if (!FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()
|| getRelatedTabs(tab.getId()).size() == 1) {
RecordUserAction.record(
"MobileTabSwitched." + TabSwitcherCoordinator.COMPONENT_NAME);
}
}
}
@Override
public boolean overviewVisible() {
return mContainerViewModel.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) {
if (!animate) mContainerViewModel.set(ANIMATE_VISIBILITY_CHANGES, false);
setVisibility(false);
mContainerViewModel.set(ANIMATE_VISIBILITY_CHANGES, true);
if (mTabGridDialogController != null) {
// Don't wait until didSelectTab(), which is after the GTS animation.
// We need to hide the dialog immediately.
mTabGridDialogController.hideDialog(false);
}
}
void registerFirstMeaningfulPaintRecorder() {
ThreadUtils.assertOnUiThread();
if (mFirstMeaningfulPaintRecorder == null) return;
if (mRegisteredFirstMeaningfulPaintRecorder) return;
mRegisteredFirstMeaningfulPaintRecorder = true;
assert mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.isTabModelRestored();
if (mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter().getCount()
== 0) {
FirstDrawDetector.waitForFirstDraw(
mContainerView, this::notifyOnFirstMeaningfulPaintNoTab);
} else {
mTabContentManager.addOnLastThumbnailListener(this::notifyOnFirstMeaningfulPaint);
}
}
private void notifyOnFirstMeaningfulPaintNoTab() {
notifyOnFirstMeaningfulPaint(0);
}
private void notifyOnFirstMeaningfulPaint(int numOfThumbnails) {
ThreadUtils.assertOnUiThread();
mFirstMeaningfulPaintRecorder.record(numOfThumbnails);
mFirstMeaningfulPaintRecorder = null;
}
boolean prepareOverview() {
mHandler.removeCallbacks(mSoftClearTabListRunnable);
mHandler.removeCallbacks(mClearTabListRunnable);
boolean quick = false;
if (mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.isTabModelRestored()) {
if (TabFeatureUtilities.isTabToGtsAnimationEnabled()) {
quick = mResetHandler.resetWithTabList(
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(),
false, mShowTabsInMruOrder);
}
setInitialScrollIndexOffset();
}
return quick;
}
private void setInitialScrollIndexOffset() {
int initialPosition = Math.max(
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter().index()
- INITIAL_SCROLL_INDEX_OFFSET,
0);
// In MRU order, selected Tab is always at the first position.
if (mShowTabsInMruOrder) initialPosition = 0;
mContainerViewModel.set(INITIAL_SCROLL_INDEX, initialPosition);
}
@Override
public void showOverview(boolean animate) {
if (mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.isTabModelRestored()) {
mResetHandler.resetWithTabList(
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(),
TabFeatureUtilities.isTabToGtsAnimationEnabled(), mShowTabsInMruOrder);
}
if (!animate) mContainerViewModel.set(ANIMATE_VISIBILITY_CHANGES, false);
setVisibility(true);
mModelIndexWhenShown = mTabModelSelector.getCurrentModelIndex();
mTabIdWhenShown = mTabModelSelector.getCurrentTabId();
mContainerViewModel.set(ANIMATE_VISIBILITY_CHANGES, true);
if (mIphProvider != null) {
mIphProvider.maybeShowIPH(mTabModelSelector.isIncognitoSelected());
}
recordTabCounts();
}
@Override
public void startedShowing(boolean isAnimating) {
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.startedShowing();
}
}
@Override
public void finishedShowing() {
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.finishedShowing();
}
}
@Override
public void startedHiding(boolean isAnimating) {
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.startedHiding();
}
}
@Override
public void finishedHiding() {
for (TabSwitcher.OverviewModeObserver observer : mObservers) {
observer.finishedHiding();
}
}
@Override
public boolean onBackPressed() {
if (!mContainerViewModel.get(IS_VISIBLE)) return false;
if (mTabSelectionEditorController.handleBackPressed()) return true;
if (mTabGridDialogController != null && mTabGridDialogController.handleBackPressed()) {
return true;
}
if (mTabModelSelector.getCurrentTab() == null) return false;
recordUserSwitchedTab(
mTabModelSelector.getCurrentTab(), mTabModelSelector.getCurrentTabId());
onTabSelecting(mTabModelSelector.getCurrentTabId());
return true;
}
@Override
public void enableRecordingFirstMeaningfulPaint(long activityCreateTimeMs) {
mFirstMeaningfulPaintRecorder = new FirstMeaningfulPaintRecorder(activityCreateTimeMs);
}
/**
* Do clean-up work after the overview hiding animation is finished.
* @see TabSwitcher.TabListDelegate#postHiding
*/
void postHiding() {
Log.d(TAG, "SoftCleanupDelay = " + getSoftCleanupDelay());
mHandler.postDelayed(mSoftClearTabListRunnable, getSoftCleanupDelay());
Log.d(TAG, "CleanupDelay = " + getCleanupDelay());
mHandler.postDelayed(mClearTabListRunnable, getCleanupDelay());
}
/**
* Set the delay for soft cleanup.
*/
void setSoftCleanupDelayForTesting(int timeoutMs) {
mSoftCleanupDelayMsForTesting = timeoutMs;
}
/**
* Set the delay for lazy cleanup.
*/
void setCleanupDelayForTesting(int timeoutMs) {
mCleanupDelayMsForTesting = timeoutMs;
}
/**
* Setup the drag-and-drop IPH provider.
*/
void setIphProvider(IphProvider iphProvider) {
mIphProvider = iphProvider;
}
/**
* Check if tabs should show in MRU order in current start surface tab switcher.
* @return whether tabs should show in MRU order
*/
static boolean isShowingTabsInMRUOrder() {
String feature = ChromeFeatureList.getFieldTrialParamByFeature(
ChromeFeatureList.START_SURFACE_ANDROID, "start_surface_variation");
return feature.equals("twopanes") || feature.equals("single");
}
/**
* Destroy any members that needs clean up.
*/
public void destroy() {
mTabModelSelector.removeObserver(mTabModelSelectorObserver);
mFullscreenManager.removeListener(mFullscreenListener);
mTabModelSelector.getTabModelFilterProvider().removeTabModelFilterObserver(
mTabModelObserver);
}
void setOnTabSelectingListener(TabSwitcher.OnTabSelectingListener listener) {
mOnTabSelectingListener = listener;
}
// GridCardOnClickListenerProvider implementation.
@Override
@Nullable
public TabListMediator.TabActionListener openTabGridDialog(Tab tab) {
if (!FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) return null;
if (!ableToOpenDialog(tab)) return null;
assert getRelatedTabs(tab.getId()).size() != 1;
assert mTabGridDialogController != null;
return tabId -> {
List<Tab> relatedTabs = getRelatedTabs(tabId);
if (relatedTabs.size() == 0) {
relatedTabs = null;
}
mTabGridDialogController.resetWithListOfTabs(relatedTabs);
RecordUserAction.record("TabGridDialog.ExpandedFromSwitcher");
};
}
@Override
public void onTabSelecting(int tabId) {
mIsSelectingInTabSwitcher = true;
mOnTabSelectingListener.onTabSelecting(LayoutManager.time(), tabId);
}
@Nullable
TabListMediator.TabActionListener getCreateGroupButtonOnClickListener(Tab tab) {
if (!ableToCreateGroup(tab) || FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
return null;
}
return tabId -> {
Tab parentTab = TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId);
mTabModelSelector.getCurrentModel().commitAllTabClosures();
mTabModelSelector.openNewTab(new LoadUrlParams(UrlConstants.NTP_URL),
TabLaunchType.FROM_CHROME_UI, parentTab,
mTabModelSelector.isIncognitoSelected());
RecordUserAction.record("TabGroup.Created.TabSwitcher");
};
}
private boolean ableToCreateGroup(Tab tab) {
return FeatureUtilities.isTabGroupsAndroidEnabled()
&& mTabModelSelector.isIncognitoSelected() == tab.isIncognito()
&& getRelatedTabs(tab.getId()).size() == 1;
}
private boolean ableToOpenDialog(Tab tab) {
return FeatureUtilities.isTabGroupsAndroidEnabled()
&& mTabModelSelector.isIncognitoSelected() == tab.isIncognito()
&& getRelatedTabs(tab.getId()).size() != 1;
}
private List<Tab> getRelatedTabs(int tabId) {
return mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter()
.getRelatedTabList(tabId);
}
private void recordTabCounts() {
final TabModel model = mTabModelSelector.getCurrentModel();
if (model == null) return;
RecordHistogram.recordCountHistogram(TAB_COUNT_HISTOGRAM, model.getCount());
final TabModelFilter filter =
mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter();
if (filter == null) return;
RecordHistogram.recordCountHistogram(TAB_ENTRIES_HISTOGRAM, filter.getCount());
}
}