blob: cd046de982c20159821c7b84f00de780d80d17eb [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.features.start_surface;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.SystemClock;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.Log;
import org.chromium.base.MathUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.jank_tracker.JankScenario;
import org.chromium.base.jank_tracker.JankTracker;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer;
import org.chromium.chrome.browser.layouts.EventFilter;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimator;
import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil;
import org.chromium.chrome.browser.tasks.TasksSurface;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher.TabListDelegate;
import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.components.browser_ui.widget.animation.Interpolators;
import org.chromium.components.version_info.VersionInfo;
import org.chromium.ui.resources.ResourceManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
/**
* A {@link Layout} that shows all tabs in one grid or carousel view.
*/
public class StartSurfaceLayout extends Layout {
private static final String TAG = "SSLayout";
// Duration of the transition animation
public static final long ZOOMING_DURATION = 300;
private static final int BACKGROUND_FADING_DURATION_MS = 150;
private static final String TRACE_SHOW_TAB_SWITCHER = "StartSurfaceLayout.Show.TabSwitcher";
private static final String TRACE_HIDE_TAB_SWITCHER = "StartSurfaceLayout.Hide.TabSwitcher";
private static final String TRACE_SHOW_START_SURFACE = "StartSurfaceLayout.Show.StartSurface";
private static final String TRACE_HIDE_START_SURFACE = "StartSurfaceLayout.Hide.StartSurface";
// The transition animation from a tab to the tab switcher.
private AnimatorSet mTabToSwitcherAnimation;
private boolean mIsAnimating;
private TabListSceneLayer mSceneLayer;
private final StartSurface mStartSurface;
private final JankTracker mJankTracker;
private final StartSurface.Controller mController;
private final StartSurface.OverviewModeObserver mStartSurfaceObserver;
// Always use getGridTabListDelegate() instead to make sure it's not null.
@Nullable
private TabSwitcher.TabListDelegate mGridTabListDelegate;
// Always use getCarouselOrSingleTabListDelegate() instead to make sure it's not null.
@Nullable
private TabSwitcher.TabListDelegate mCarouselOrSingleTabListDelegate;
// To force Toolbar finishes its animation when this Layout finished hiding.
private final LayoutTab mDummyLayoutTab;
private boolean mIsInitialized;
private float mBackgroundAlpha;
private int mFrameCount;
private long mStartTime;
private long mLastFrameTime;
private long mMaxFrameInterval;
private int mStartFrame;
private float mThumbnailAspectRatio;
private boolean mAndroidViewFinishedShowing;
private Animator mBackgroundTabAnimation;
interface PerfListener {
void onAnimationDone(
int frameRendered, long elapsedMs, long maxFrameInterval, int dirtySpan);
}
private PerfListener mPerfListenerForTesting;
public StartSurfaceLayout(Context context, LayoutUpdateHost updateHost,
LayoutRenderHost renderHost, StartSurface startSurface, JankTracker jankTracker) {
super(context, updateHost, renderHost);
mDummyLayoutTab = createLayoutTab(Tab.INVALID_TAB_ID, false, false, false);
mDummyLayoutTab.setShowToolbar(true);
mStartSurface = startSurface;
mStartSurface.setOnTabSelectingListener(this::onTabSelecting);
mController = mStartSurface.getController();
mJankTracker = jankTracker;
mStartSurfaceObserver = new StartSurface.OverviewModeObserver() {
@Override
public void startedShowing() {
mAndroidViewFinishedShowing = false;
}
@Override
public void finishedShowing() {
mAndroidViewFinishedShowing = true;
doneShowing();
// The Tab-to-GTS animation is done, and it's time to renew the thumbnail without
// causing janky frames. When animation is off, the thumbnail is already updated
// when showing the GTS.
if (TabUiFeatureUtilities.isTabToGtsAnimationEnabled()) {
// Delay thumbnail taking a bit more to make it less likely to happen before the
// thumbnail taking triggered by ThumbnailFetcher. See crbug.com/996385 for
// details.
new Handler().postDelayed(() -> {
Tab currentTab = mTabModelSelector.getCurrentTab();
if (currentTab != null) mTabContentManager.cacheTabThumbnail(currentTab);
mLayoutTabs = null;
}, ZOOMING_DURATION);
} else {
// crbug.com/1176548, mLayoutTabs is used to capture thumbnail, null it in a
// post delay handler to avoid creating a new pending surface in native, which
// will hold the thumbnail capturing task.
new Handler().postDelayed(() -> { mLayoutTabs = null; }, ZOOMING_DURATION);
}
}
@Override
public void startedHiding() {}
@Override
public void finishedHiding() {
// The Android View version of GTS overview is hidden.
// If not doing GTS-to-Tab transition animation or start surface homepage is hiding
// (instead of grid tab switcher), we show the fade-out instead, which was already
// done.
if (!TabUiFeatureUtilities.isTabToGtsAnimationEnabled()
|| isHidingStartSurfaceHomepage()) {
postHiding();
return;
}
// If we are doing GTS-to-Tab transition animation, we start showing the Bitmap
// version of the GTS overview in the background while expanding the thumbnail to
// the viewport.
expandTab(getThumbnailLocationOfCurrentTab());
}
};
mController.addOverviewModeObserver(mStartSurfaceObserver);
if (TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()) {
mThumbnailAspectRatio = (float) TabUiFeatureUtilities.THUMBNAIL_ASPECT_RATIO.getValue();
mThumbnailAspectRatio = MathUtils.clamp(mThumbnailAspectRatio, 0.5f, 2.0f);
}
}
@Override
public void onFinishNativeInitialization() {
if (mIsInitialized) return;
mIsInitialized = true;
mStartSurface.initWithNative();
ensureSceneLayerCreated();
mSceneLayer.setTabModelSelector(mTabModelSelector);
}
// Layout implementation.
@Override
public void setTabModelSelector(TabModelSelector modelSelector, TabContentManager manager) {
super.setTabModelSelector(modelSelector, manager);
if (mSceneLayer != null) {
mSceneLayer.setTabModelSelector(modelSelector);
}
}
@Override
public LayoutTab getLayoutTab(int id) {
return mDummyLayoutTab;
}
@Override
public void destroy() {
if (mController != null) {
mController.removeOverviewModeObserver(mStartSurfaceObserver);
}
}
@Override
public void show(long time, boolean animate) {
boolean isShowingStartSurfaceHomepage = isShowingStartSurfaceHomepage();
if (isShowingStartSurfaceHomepage) {
showStartSurface(time, animate);
} else {
showTabSwitcher(time, animate);
}
}
private void showStartSurface(long time, boolean animate) {
try (TraceEvent e = TraceEvent.scoped(TRACE_SHOW_START_SURFACE)) {
show(time, animate, true /*isShowingStartSurfaceHomepage*/);
}
}
private void showTabSwitcher(long time, boolean animate) {
try (TraceEvent e = TraceEvent.scoped(TRACE_SHOW_TAB_SWITCHER)) {
show(time, animate, false /*isShowingStartSurfaceHomepage*/);
}
}
private void show(long time, boolean animate, boolean isShowingStartSurfaceHomepage) {
super.show(time, animate);
// When shown on StartSurface jank is tracked under
// JankScenario.START_SURFACE_TAB_SWITCHER and it's started/stopped on
// StartSurfaceMediator.
if (!StartSurfaceConfiguration.isStartSurfaceFlagEnabled()) {
mJankTracker.startTrackingScenario(JankScenario.TAB_SWITCHER);
}
// Lazy initialization if needed.
mStartSurface.initialize();
// Keep the current tab in mLayoutTabs even if we are not going to show the shrinking
// animation so that thumbnail taking is not blocked.
LayoutTab sourceLayoutTab = createLayoutTab(mTabModelSelector.getCurrentTabId(),
mTabModelSelector.isIncognitoSelected(), NO_CLOSE_BUTTON, NO_TITLE);
sourceLayoutTab.setDecorationAlpha(0);
mLayoutTabs = new LayoutTab[] {sourceLayoutTab};
boolean quick;
// If start surface homepage is showing, carousel or single tab switcher is used.
// Otherwise grid tab switcher is used.
if (isShowingStartSurfaceHomepage) {
quick = getCarouselOrSingleTabListDelegate().prepareOverview();
} else {
quick = getGridTabListDelegate().prepareOverview();
}
// Skip shrinking animation when there is no tab in current tab model. If it's showing
// start surface, we don't show the shrink tab animation.
boolean isCurrentTabModelEmpty = mTabModelSelector.getCurrentModel().getCount() == 0;
boolean showShrinkingAnimation = animate
&& TabUiFeatureUtilities.isTabToGtsAnimationEnabled() && !isCurrentTabModelEmpty
&& !isShowingStartSurfaceHomepage;
boolean skipSlowZooming = TabUiFeatureUtilities.SKIP_SLOW_ZOOMING.getValue();
Log.d(TAG, "SkipSlowZooming = " + skipSlowZooming);
if (skipSlowZooming) {
showShrinkingAnimation &= quick;
}
if (TabUiFeatureUtilities.isLaunchPolishEnabled()) {
// Intentionally disable the shrinking animation when accessibility is enabled.
// During the shrinking animation, since the ComponsitorViewHolder is not focusable,
// I think we are in a temporary no "valid" focus target state, so the focus shifts
// to the omnibox and triggers an accessibility announcement of the URL and a
// keyboard hiding event. Disable the animation to avoid this temporary state.
showShrinkingAnimation &= !ChromeAccessibilityUtil.get().isAccessibilityEnabled();
}
if (!showShrinkingAnimation) {
mController.showOverview(animate);
return;
}
shrinkTab(animate, () -> getGridTabListDelegate().getThumbnailLocationOfCurrentTab(false));
}
@Override
protected void updateLayout(long time, long dt) {
ensureSceneLayerCreated();
super.updateLayout(time, dt);
if (mLayoutTabs == null) return;
assert mLayoutTabs.length >= 1;
boolean needUpdate = updateSnap(dt, mLayoutTabs[0]);
if (needUpdate) requestUpdate();
}
@Override
public void startHiding(int nextId, boolean hintAtTabSelection) {
int startSurfaceState = mController.getStartSurfaceState();
StartSurfaceUserData.getInstance().setUnusedTabRestoredAtStartup(false);
if (startSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) {
startHidingStartSurface(nextId, hintAtTabSelection);
} else {
startHidingTabSwitcher(nextId, hintAtTabSelection);
}
}
private void startHidingStartSurface(int nextId, boolean hintAtTabSelection) {
try (TraceEvent e = TraceEvent.scoped(TRACE_HIDE_START_SURFACE)) {
startHidingImpl(nextId, hintAtTabSelection);
}
}
private void startHidingTabSwitcher(int nextId, boolean hintAtTabSelection) {
try (TraceEvent e = TraceEvent.scoped(TRACE_HIDE_TAB_SWITCHER)) {
startHidingImpl(nextId, hintAtTabSelection);
}
}
private void startHidingImpl(int nextId, boolean hintAtTabSelection) {
super.startHiding(nextId, hintAtTabSelection);
int sourceTabId = nextId;
if (sourceTabId == Tab.INVALID_TAB_ID) {
sourceTabId = mTabModelSelector.getCurrentTabId();
}
LayoutTab sourceLayoutTab = createLayoutTab(
sourceTabId, mTabModelSelector.isIncognitoSelected(), NO_CLOSE_BUTTON, NO_TITLE);
sourceLayoutTab.setDecorationAlpha(0);
List<LayoutTab> layoutTabs = new ArrayList<>();
layoutTabs.add(sourceLayoutTab);
if (sourceTabId != mTabModelSelector.getCurrentTabId()) {
// Keep the original tab in mLayoutTabs to unblock thumbnail taking at the end of
// the animation.
LayoutTab originalTab = createLayoutTab(mTabModelSelector.getCurrentTabId(),
mTabModelSelector.isIncognitoSelected(), NO_CLOSE_BUTTON, NO_TITLE);
originalTab.setScale(0);
originalTab.setDecorationAlpha(0);
layoutTabs.add(originalTab);
}
mLayoutTabs = layoutTabs.toArray(new LayoutTab[0]);
updateCacheVisibleIds(new LinkedList<>(Arrays.asList(sourceTabId)));
mIsAnimating = true;
mController.hideOverview(!TabUiFeatureUtilities.isTabToGtsAnimationEnabled());
}
@Override
public void doneHiding() {
try (TraceEvent e = TraceEvent.scoped("StartSurfaceLayout.DoneHiding")) {
super.doneHiding();
RecordUserAction.record("MobileExitStackView");
// When shown on StartSurface jank is tracked under
// JankScenario.START_SURFACE_TAB_SWITCHER and it's started/stopped on
// StartSurfaceMediator.
if (!StartSurfaceConfiguration.isStartSurfaceFlagEnabled()) {
mJankTracker.finishTrackingScenario(JankScenario.TAB_SWITCHER);
}
}
}
@Override
public void doneShowing() {
try (TraceEvent e = TraceEvent.scoped("StartSurfaceLayout.DoneShowing")) {
if (!mAndroidViewFinishedShowing) return;
super.doneShowing();
}
}
@Override
public boolean onBackPressed() {
return mController.onBackPressed();
}
@Override
protected EventFilter getEventFilter() {
return null;
}
@Override
protected SceneLayer getSceneLayer() {
return mSceneLayer;
}
private void ensureSceneLayerCreated() {
if (mSceneLayer != null) return;
mSceneLayer = new TabListSceneLayer();
}
@Override
public boolean handlesTabClosing() {
return true;
}
@Override
public boolean handlesTabCreating() {
return true;
}
@Override
public boolean handlesCloseAll() {
return false;
}
@Override
protected void forceAnimationToFinish() {
super.forceAnimationToFinish();
if (mTabToSwitcherAnimation != null) {
if (mTabToSwitcherAnimation.isRunning()) mTabToSwitcherAnimation.end();
}
}
@Override
public void onTabCreated(long time, int id, int index, int sourceId, boolean newIsIncognito,
boolean background, float originX, float originY) {
super.onTabCreated(time, id, index, sourceId, newIsIncognito, background, originX, originY);
if (!background || newIsIncognito
|| mController.getStartSurfaceState() != StartSurfaceState.SHOWN_HOMEPAGE) {
return;
}
TasksSurface primaryTasksSurface = mStartSurface.getPrimaryTasksSurface();
assert primaryTasksSurface != null;
if (mBackgroundTabAnimation != null && mBackgroundTabAnimation.isStarted()) {
mBackgroundTabAnimation.end();
}
mBackgroundTabAnimation =
BackgroundTabAnimation.create(this, (ViewGroup) primaryTasksSurface.getView(),
originX, originY, getOrientation() == Orientation.PORTRAIT);
mBackgroundTabAnimation.start();
}
/**
* Animate shrinking a tab to a target {@link Rect} area.
* @param animate Whether to play an entry animation.
* @param target The target {@link Rect} area.
*/
private void shrinkTab(boolean animate, Supplier<Rect> target) {
if (target.get() == null) {
mController.showOverview(animate);
return;
}
forceAnimationToFinish();
LayoutTab sourceLayoutTab = mLayoutTabs[0];
CompositorAnimationHandler handler = getAnimationHandler();
Collection<Animator> animationList = new ArrayList<>(5);
// Step 1: zoom out the source tab
Supplier<Float> scaleStartValueSupplier = () -> 1.0f;
Supplier<Float> scaleEndValueSupplier = () -> target.get().width() / (getWidth() * mDpToPx);
Supplier<Float> xStartValueSupplier = () -> 0f;
Supplier<Float> xEndValueSupplier = () -> target.get().left / mDpToPx;
Supplier<Float> yStartValueSupplier = () -> 0f;
Supplier<Float> yEndValueSupplier = () -> target.get().top / mDpToPx;
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.SCALE, scaleStartValueSupplier, scaleEndValueSupplier, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.X, xStartValueSupplier, xEndValueSupplier, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.Y, yStartValueSupplier, yEndValueSupplier, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
// TODO(crbug.com/964406): when shrinking to the bottom row, bottom of the tab goes up and
// down, making the "create group" visible for a while.
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.MAX_CONTENT_HEIGHT, sourceLayoutTab.getUnclampedOriginalContentHeight(),
TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()
? Math.min(getWidth() / mThumbnailAspectRatio,
sourceLayoutTab.getUnclampedOriginalContentHeight())
: getWidth(),
ZOOMING_DURATION, Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
CompositorAnimator backgroundAlpha =
CompositorAnimator.ofFloat(handler, 0f, 1f, BACKGROUND_FADING_DURATION_MS,
animator -> mBackgroundAlpha = animator.getAnimatedValue());
backgroundAlpha.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animationList.add(backgroundAlpha);
mTabToSwitcherAnimation = new AnimatorSet();
mTabToSwitcherAnimation.playTogether(animationList);
mTabToSwitcherAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTabToSwitcherAnimation = null;
// Step 2: fade in the real GTS RecyclerView.
mController.showOverview(true);
reportAnimationPerf(true);
}
});
mStartFrame = mFrameCount;
mStartTime = SystemClock.elapsedRealtime();
mLastFrameTime = SystemClock.elapsedRealtime();
mMaxFrameInterval = 0;
mTabToSwitcherAnimation.start();
}
/**
* Animate expanding a tab from a source {@link Rect} area.
* @param source The source {@link Rect} area.
*/
private void expandTab(Rect source) {
LayoutTab sourceLayoutTab = mLayoutTabs[0];
forceAnimationToFinish();
CompositorAnimationHandler handler = getAnimationHandler();
Collection<Animator> animationList = new ArrayList<>(5);
// Zoom in the source tab
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.SCALE, source.width() / (getWidth() * mDpToPx), 1, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.X, source.left / mDpToPx, 0f, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.Y, source.top / mDpToPx, 0f, ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
// TODO(crbug.com/964406): when shrinking to the bottom row, bottom of the tab goes up and
// down, making the "create group" visible for a while.
animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab,
LayoutTab.MAX_CONTENT_HEIGHT,
TabUiFeatureUtilities.isTabThumbnailAspectRatioNotOne()
? Math.min(getWidth() / mThumbnailAspectRatio,
sourceLayoutTab.getUnclampedOriginalContentHeight())
: getWidth(),
sourceLayoutTab.getUnclampedOriginalContentHeight(), ZOOMING_DURATION,
Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR));
CompositorAnimator backgroundAlpha =
CompositorAnimator.ofFloat(handler, 1f, 0f, BACKGROUND_FADING_DURATION_MS,
animator -> mBackgroundAlpha = animator.getAnimatedValue());
backgroundAlpha.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animationList.add(backgroundAlpha);
mTabToSwitcherAnimation = new AnimatorSet();
mTabToSwitcherAnimation.playTogether(animationList);
mTabToSwitcherAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTabToSwitcherAnimation = null;
postHiding();
reportAnimationPerf(false);
}
});
mStartFrame = mFrameCount;
mStartTime = SystemClock.elapsedRealtime();
mLastFrameTime = SystemClock.elapsedRealtime();
mMaxFrameInterval = 0;
mTabToSwitcherAnimation.start();
}
private Rect getThumbnailLocationOfCurrentTab() {
if (isHidingStartSurfaceHomepage()) {
return getCarouselOrSingleTabListDelegate().getThumbnailLocationOfCurrentTab(true);
} else {
return getGridTabListDelegate().getThumbnailLocationOfCurrentTab(true);
}
}
private TabListDelegate getCarouselOrSingleTabListDelegate() {
if (mCarouselOrSingleTabListDelegate == null) {
mCarouselOrSingleTabListDelegate = mStartSurface.getCarouselOrSingleTabListDelegate();
}
return mCarouselOrSingleTabListDelegate;
}
private TabListDelegate getGridTabListDelegate() {
if (mGridTabListDelegate == null) {
mGridTabListDelegate = mStartSurface.getGridTabListDelegate();
}
return mGridTabListDelegate;
}
private TabListDelegate getLastUsedTabListDelegate() {
// It is possible that the StartSurfaceState becomes StartSurfaceState.NOT_SHOWN when hiding
// the overview page, thus, the last used TabListDelegate is returned.
if (mController.getStartSurfaceState() == StartSurfaceState.NOT_SHOWN) {
assert mGridTabListDelegate != null || mCarouselOrSingleTabListDelegate != null;
return mGridTabListDelegate != null ? mGridTabListDelegate
: mCarouselOrSingleTabListDelegate;
}
return isShowingStartSurfaceHomepage() ? getCarouselOrSingleTabListDelegate()
: getGridTabListDelegate();
}
/**
* When state is SHOWN_HOMEPAGE or SHOWING_HOMEPAGE or SHOWING_START, state surface homepage is
* showing. When state is StartSurfaceState.SHOWING_PREVIOUS and the previous state is
* SHOWN_HOMEPAGE or NOT_SHOWN, homepage is showing.
* @return Whether start surface homepage is showing.
*/
private boolean isShowingStartSurfaceHomepage() {
@StartSurfaceState
int currentState = mController.getStartSurfaceState();
@StartSurfaceState
int previousState = mController.getPreviousStartSurfaceState();
return currentState == StartSurfaceState.SHOWN_HOMEPAGE
|| currentState == StartSurfaceState.SHOWING_HOMEPAGE
|| currentState == StartSurfaceState.SHOWING_START
|| (currentState == StartSurfaceState.SHOWING_PREVIOUS
&& (previousState == StartSurfaceState.SHOWN_HOMEPAGE
|| previousState == StartSurfaceState.NOT_SHOWN));
}
private boolean isHidingStartSurfaceHomepage() {
return mController.getPreviousStartSurfaceState() == StartSurfaceState.SHOWN_HOMEPAGE;
}
private void postHiding() {
if (ReturnToChromeExperimentsUtil.isStartSurfaceEnabled(getContext())) {
mStartSurface.onHide();
} else {
getGridTabListDelegate().postHiding();
}
mIsAnimating = false;
doneHiding();
}
@VisibleForTesting
void setPerfListenerForTesting(PerfListener perfListener) {
mPerfListenerForTesting = perfListener;
}
@VisibleForTesting
public StartSurface getStartSurfaceForTesting() {
return mStartSurface;
}
private void reportAnimationPerf(boolean isShrinking) {
int frameRendered = mFrameCount - mStartFrame;
long elapsedMs = SystemClock.elapsedRealtime() - mStartTime;
// If it's hiding start surface, TabListDelegate for carousel/single tab switcher should be
// used.
long lastDirty = isHidingStartSurfaceHomepage()
? getCarouselOrSingleTabListDelegate().getLastDirtyTime()
: getGridTabListDelegate().getLastDirtyTime();
int dirtySpan = (int) (lastDirty - mStartTime);
float fps = 1000.f * frameRendered / elapsedMs;
String message = String.format(Locale.US,
"fps = %.2f (%d / %dms), maxFrameInterval = %d, dirtySpan = %d", fps, frameRendered,
elapsedMs, mMaxFrameInterval, dirtySpan);
// TODO(crbug.com/964406): stop logging it after this feature stabilizes.
if (!VersionInfo.isStableBuild()) {
Log.i(TAG, message);
}
String suffix;
if (isShrinking) {
suffix = ".Shrink";
} else {
suffix = ".Expand";
}
// TODO(crbug.com/982018): Separate histograms for carousel tab switcher.
RecordHistogram.recordCount100Histogram(
"GridTabSwitcher.FramePerSecond" + suffix, (int) fps);
RecordHistogram.recordTimesHistogram(
"GridTabSwitcher.MaxFrameInterval" + suffix, mMaxFrameInterval);
RecordHistogram.recordTimesHistogram("GridTabSwitcher.DirtySpan" + suffix, dirtySpan);
if (mPerfListenerForTesting != null) {
mPerfListenerForTesting.onAnimationDone(
frameRendered, elapsedMs, mMaxFrameInterval, dirtySpan);
}
}
@Override
protected void updateSceneLayer(RectF viewport, RectF contentViewport,
LayerTitleCache layerTitleCache, TabContentManager tabContentManager,
ResourceManager resourceManager, BrowserControlsStateProvider browserControls) {
ensureSceneLayerCreated();
super.updateSceneLayer(viewport, contentViewport, layerTitleCache, tabContentManager,
resourceManager, browserControls);
assert mSceneLayer != null;
TabListDelegate currentTabListDelegate = getLastUsedTabListDelegate();
// The content viewport is intentionally sent as both params below.
mSceneLayer.pushLayers(getContext(), contentViewport, contentViewport, this,
layerTitleCache, tabContentManager, resourceManager, browserControls,
TabUiFeatureUtilities.isTabToGtsAnimationEnabled()
? currentTabListDelegate.getResourceId()
: 0,
mBackgroundAlpha, currentTabListDelegate.getTabListTopOffset());
mFrameCount++;
if (mLastFrameTime != 0) {
long elapsed = SystemClock.elapsedRealtime() - mLastFrameTime;
mMaxFrameInterval = Math.max(mMaxFrameInterval, elapsed);
}
mLastFrameTime = SystemClock.elapsedRealtime();
}
@Override
public int getLayoutType() {
return LayoutType.TAB_SWITCHER;
}
@Override
public boolean onUpdateAnimation(long time, boolean jumpToEnd) {
return mTabToSwitcherAnimation == null && !mIsAnimating;
}
@Override
public boolean canHostBeFocusable() {
if (TabUiFeatureUtilities.isLaunchPolishEnabled()
&& ChromeAccessibilityUtil.get().isAccessibilityEnabled()) {
// We don't allow this layout to gain focus when accessibility is enabled so that the
// CompositorViewHolder doesn't steal focus when entering tab switcher.
// (crbug.com/1125185).
return false;
}
return super.canHostBeFocusable();
}
}