From e87782768b41600d1d46ae08199554af6cd44e82 Mon Sep 17 00:00:00 2001 From: samartnik Date: Thu, 10 Feb 2022 10:45:18 -0500 Subject: [PATCH] [Android] Removed stack layout code Resolves https://github.com/brave/brave-browser/issues/21005 Chromium change: https://github.com/chromium/chromium/commit/94a231f90d89f9af2996172fae508f9472d573cb Remove unused rendering code in TabLayout Follow up to https://chromium-review.googlesource.com/c/chromium/src/+/3388174 and continues to simplify the Tab rendering logic for phones. Also deletes anything related to tilting/rotating and card backs for the tabs. Bug: 1287653 --- android/brave_java_resources.gni | 4 - android/brave_java_sources.gni | 8 - .../BraveTabUiFeatureUtilities.java | 35 - android/java/apk_for_test.flags | 14 - .../chrome/browser/app/BraveActivity.java | 8 - .../layouts/BraveLayoutManagerChrome.java | 81 - .../compositor/layouts/phone/StackLayout.java | 190 -- .../layouts/phone/StackLayoutBase.java | 1709 -------------- .../layouts/phone/stack/OverlappingStack.java | 671 ------ .../compositor/layouts/phone/stack/Stack.java | 2064 ----------------- .../layouts/phone/stack/StackAnimation.java | 640 ----- .../layouts/phone/stack/StackTab.java | 524 ----- .../phone/stack/StackViewAnimation.java | 110 - .../settings/AppearancePreferences.java | 21 - android/java/res/values-hdpi/brave_dimens.xml | 10 - .../java/res/values-xhdpi/brave_dimens.xml | 10 - .../java/res/values-xxhdpi/brave_dimens.xml | 10 - android/java/res/values/brave_dimens.xml | 21 - android/java/res/values/brave_values.xml | 13 - .../java/res/xml/appearance_preferences.xml | 6 - .../chromium/chrome/browser/BytecodeTest.java | 23 - .../android/strings/android_brave_strings.grd | 3 - build/android/bytecode/BUILD.gn | 2 - .../org/brave/bytecode/BraveClassAdapter.java | 2 - .../BraveLayoutManagerChromeClassAdapter.java | 21 - ...raveTabUiFeatureUtilitiesClassAdapter.java | 25 - ...me-browser-ChromeTabbedActivity.java.patch | 12 - 27 files changed, 6237 deletions(-) delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackTab.java delete mode 100644 android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackViewAnimation.java delete mode 100644 android/java/res/values-hdpi/brave_dimens.xml delete mode 100644 android/java/res/values-xhdpi/brave_dimens.xml delete mode 100644 android/java/res/values-xxhdpi/brave_dimens.xml delete mode 100644 android/java/res/values/brave_values.xml delete mode 100644 build/android/bytecode/java/org/brave/bytecode/BraveLayoutManagerChromeClassAdapter.java delete mode 100644 build/android/bytecode/java/org/brave/bytecode/BraveTabUiFeatureUtilitiesClassAdapter.java delete mode 100644 patches/chrome-android-java-src-org-chromium-chrome-browser-ChromeTabbedActivity.java.patch diff --git a/android/brave_java_resources.gni b/android/brave_java_resources.gni index 1439ed3de857..4b116844c422 100644 --- a/android/brave_java_resources.gni +++ b/android/brave_java_resources.gni @@ -924,20 +924,16 @@ brave_java_resources = [ "java/res/values-h748dp/dimens.xml", "java/res/values-h765dp/dimens.xml", "java/res/values-h820dp/dimens.xml", - "java/res/values-hdpi/brave_dimens.xml", "java/res/values-night/brave_colors.xml", "java/res/values-night/brave_styles.xml", "java/res/values-sw600dp/brave_dimens.xml", "java/res/values-v21/brave_styles.xml", - "java/res/values-xhdpi/brave_dimens.xml", - "java/res/values-xxhdpi/brave_dimens.xml", "java/res/values/array.xml", "java/res/values/brave_attrs.xml", "java/res/values/brave_colors.xml", "java/res/values/brave_dimens.xml", "java/res/values/brave_ids.xml", "java/res/values/brave_styles.xml", - "java/res/values/brave_values.xml", "java/res/xml/appearance_preferences.xml", "java/res/xml/background_images_preferences.xml", "java/res/xml/brave_download_preferences.xml", diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index 5f062cbdb010..1e3c9f349616 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -54,14 +54,6 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/brave_news/models/FeedItemsCard.java", "../../brave/android/java/org/chromium/chrome/browser/brave_stats/BraveStatsBottomSheetDialogFragment.java", "../../brave/android/java/org/chromium/chrome/browser/brave_stats/BraveStatsUtil.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackTab.java", - "../../brave/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackViewAnimation.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/AssetRatioServiceFactory.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/BlockchainRegistryFactory.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/BraveWalletServiceFactory.java", diff --git a/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/BraveTabUiFeatureUtilities.java b/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/BraveTabUiFeatureUtilities.java index ca3d8c4d7c5a..aac70f5467cd 100644 --- a/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/BraveTabUiFeatureUtilities.java +++ b/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/BraveTabUiFeatureUtilities.java @@ -6,49 +6,14 @@ package org.chromium.chrome.browser.tasks.tab_management; import android.annotation.SuppressLint; -import android.content.Context; import org.chromium.chrome.browser.flags.CachedFeatureFlags; -import org.chromium.chrome.browser.flags.ChromeFeatureList; -import org.chromium.chrome.browser.preferences.BravePreferenceKeys; import org.chromium.chrome.browser.preferences.SharedPreferencesManager; public class BraveTabUiFeatureUtilities { private static final String TAB_GROUP_AUTO_CREATION_PREFERENCE = "Chrome.Flags.FieldTrialParamCached.TabGridLayoutAndroid:enable_tab_group_auto_creation"; - /** - * @return Whether the Grid Tab Switcher UI is enabled and available for use. - */ - public static boolean isGridTabSwitcherEnabled(Context context) { - if (!isTabGroupsAndroidEnabled(context)) { - return false; - } - return TabUiFeatureUtilities.isGridTabSwitcherEnabled(context); - } - - /** - * @return Whether the tab group feature is enabled and available for use. - */ - public static boolean isTabGroupsAndroidEnabled(Context context) { - // For backward compatibility we take value of Tab Grid feature if BRAVE_TAB_GROUPS_ENABLED - // setting hasn't been created. We don't want to rely on Tab Grid feature itself since it - // can be removed in the upstream going forward. - if (!SharedPreferencesManager.getInstance().contains( - BravePreferenceKeys.BRAVE_TAB_GROUPS_ENABLED)) { - SharedPreferencesManager.getInstance().writeBoolean( - BravePreferenceKeys.BRAVE_TAB_GROUPS_ENABLED, - CachedFeatureFlags.isEnabled(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID)); - } - - if (!SharedPreferencesManager.getInstance().readBoolean( - BravePreferenceKeys.BRAVE_TAB_GROUPS_ENABLED, true)) { - return false; - } - - return TabUiFeatureUtilities.isTabGroupsAndroidEnabled(context); - } - @SuppressLint("VisibleForTests") public static void maybeOverrideEnableTabGroupAutoCreationPreference() { if (TabUiFeatureUtilities.ENABLE_TAB_GROUP_AUTO_CREATION.getValue()) { diff --git a/android/java/apk_for_test.flags b/android/java/apk_for_test.flags index a2d8b183d534..ef33c0306ba6 100644 --- a/android/java/apk_for_test.flags +++ b/android/java/apk_for_test.flags @@ -305,20 +305,6 @@ *** getModel(...); } --keep class org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone - --keep class org.chromium.chrome.browser.compositor.layouts.BraveLayoutManagerChrome - --keep class org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities { - *** isGridTabSwitcherEnabled(...); - *** isTabGroupsAndroidEnabled(...); -} - --keep class org.chromium.chrome.browser.tasks.tab_management.BraveTabUiFeatureUtilities { - *** isGridTabSwitcherEnabled(...); - *** isTabGroupsAndroidEnabled(...); -} - -keep class org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl { public (...); } diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index 112a69a6e36b..5423d36ade86 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -100,7 +100,6 @@ import org.chromium.chrome.browser.compositor.layouts.Layout; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; -import org.chromium.chrome.browser.compositor.layouts.phone.StackLayout; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletActivity; import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent; import org.chromium.chrome.browser.flags.ChromeFeatureList; @@ -1177,13 +1176,6 @@ private void SetUpdatePreferences() { editor.apply(); } - public void hideOverview(LayoutManagerChrome layoutManager) { - Layout activeLayout = layoutManager.getActiveLayout(); - if (activeLayout instanceof StackLayout) { - ((StackLayout) activeLayout).commitOutstandingModelState(LayoutManagerImpl.time()); - } - } - public ObservableSupplier getBrowserControlsManagerSupplier() { return mBrowserControlsManagerSupplier; } diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java b/android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java deleted file mode 100644 index e3b6125bd253..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome.java +++ /dev/null @@ -1,81 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts; - -import android.content.Context; -import android.view.ViewGroup; - -import androidx.annotation.Nullable; - -import org.chromium.base.jank_tracker.JankTracker; -import org.chromium.base.supplier.ObservableSupplier; -import org.chromium.base.supplier.OneshotSupplierImpl; -import org.chromium.base.supplier.Supplier; -import org.chromium.chrome.browser.app.BraveActivity; -import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; -import org.chromium.chrome.browser.compositor.LayerTitleCache; -import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; -import org.chromium.chrome.browser.compositor.layouts.phone.StackLayout; -import org.chromium.chrome.browser.layouts.LayoutStateProvider; -import org.chromium.chrome.browser.tab.Tab; -import org.chromium.chrome.browser.tabmodel.TabCreatorManager; -import org.chromium.chrome.browser.tabmodel.TabModelSelector; -import org.chromium.chrome.browser.theme.TopUiThemeColorProvider; -import org.chromium.chrome.browser.toolbar.ControlContainer; -import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; -import org.chromium.chrome.features.start_surface.StartSurface; -import org.chromium.ui.resources.dynamics.DynamicResourceLoader; - -import java.util.List; - -public class BraveLayoutManagerChrome extends LayoutManagerChrome - implements OverviewModeController, ChromeAccessibilityUtil.Observer { - /** Whether to create an overview Layout when LayoutManagerChrome is created. */ - private boolean mCreateOverviewLayout; - - public BraveLayoutManagerChrome(LayoutManagerHost host, ViewGroup contentContainer, - boolean createOverviewLayout, @Nullable StartSurface startSurface, - ObservableSupplier tabContentManagerSupplier, - OneshotSupplierImpl overviewModeBehaviorSupplier, - Supplier topUiThemeColorProvider, JankTracker jankTracker) { - super(host, contentContainer, createOverviewLayout, startSurface, tabContentManagerSupplier, - overviewModeBehaviorSupplier, topUiThemeColorProvider, jankTracker); - - mCreateOverviewLayout = createOverviewLayout && startSurface == null; - } - - @Override - public void init(TabModelSelector selector, TabCreatorManager creator, - ControlContainer controlContainer, DynamicResourceLoader dynamicResourceLoader, - TopUiThemeColorProvider topUiColorProvider) { - if (mCreateOverviewLayout) { - Context context = mHost.getContext(); - LayoutRenderHost renderHost = mHost.getLayoutRenderHost(); - final ObservableSupplier - browserControlsSupplier = - BraveActivity.getBraveActivity().getBrowserControlsManagerSupplier(); - mOverviewLayout = new StackLayout(context, this, renderHost, - (ObservableSupplier) browserControlsSupplier); - } - - super.init(selector, creator, controlContainer, dynamicResourceLoader, topUiColorProvider); - } - - @Override - public void hideOverview(boolean animate) { - Layout activeLayout = getActiveLayout(); - if (activeLayout instanceof StackLayout) { - if (animate) { - activeLayout.onTabSelecting(time(), Tab.INVALID_TAB_ID); - } else { - startHiding(Tab.INVALID_TAB_ID, false); - doneHiding(); - } - return; - } - super.hideOverview(animate); - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java deleted file mode 100644 index 1bb5cb64760a..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayout.java +++ /dev/null @@ -1,190 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone; - -import android.content.Context; - -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.base.supplier.ObservableSupplier; -import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; -import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; -import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; -import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; -import org.chromium.chrome.browser.tab.Tab; -import org.chromium.chrome.browser.tabmodel.TabList; -import org.chromium.chrome.browser.tabmodel.TabModel; -import org.chromium.chrome.browser.tabmodel.TabModelSelector; -import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; -import org.chromium.chrome.browser.tabmodel.TabModelUtils; - -import java.util.ArrayList; - -/** - * Layout that displays all normal tabs in one stack and all incognito tabs in a second. - */ -public class StackLayout extends StackLayoutBase { - public static final int NORMAL_STACK_INDEX = 0; - public static final int INCOGNITO_STACK_INDEX = 1; - - /** Whether the current fling animation is the result of switching stacks. */ - private boolean mFlingFromModelChange; - - /** Disable the incognito button while selecting a tab. */ - private boolean mAnimatingStackSwitch; - - /** - * @param context The current Android's context. - * @param updateHost The {@link LayoutUpdateHost} view for this - * layout. - * @param renderHost The {@link LayoutRenderHost} view for this - * layout. - * @param browserControlsStateProviderSupplier The {@link ObservableSupplier} for the - * {@link BrowserControlsStateProvider}. - */ - public StackLayout(Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost, - ObservableSupplier browserControlsStateProviderSupplier) { - super(context, updateHost, renderHost, browserControlsStateProviderSupplier); - } - - @Override - protected boolean shouldIgnoreTouchInput() { - return mAnimatingStackSwitch; - } - - @Override - public void setTabModelSelector(TabModelSelector modelSelector, TabContentManager manager) { - super.setTabModelSelector(modelSelector, manager); - if (modelSelector.getTabModelFilterProvider().getCurrentTabModelFilter() == null) { - // Registers an observer of the TabModel's creation if it hasn't been created yet. Once - // the TabModel is ready, we will call setTablists() immediately. - // See https://crbug.com/1142858. - TabModelSelectorObserver selectorObserver = new TabModelSelectorObserver() { - @Override - public void onChange() { - mTabModelSelector.removeObserver(this); - setTablists(); - } - }; - mTabModelSelector.addObserver(selectorObserver); - } else { - setTablists(); - } - } - - private void setTablists() { - ArrayList tabLists = new ArrayList(); - tabLists.add(mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(false)); - tabLists.add(mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(true)); - setTabLists(tabLists); - } - - @Override - protected int getTabStackIndex(int tabId) { - if (tabId == Tab.INVALID_TAB_ID) { - if (mTemporarySelectedStack != INVALID_STACK_INDEX) return mTemporarySelectedStack; - - return mTabModelSelector.isIncognitoSelected() ? INCOGNITO_STACK_INDEX - : NORMAL_STACK_INDEX; - } else { - return TabModelUtils.getTabById(mTabModelSelector.getModel(true), tabId) != null - ? INCOGNITO_STACK_INDEX - : NORMAL_STACK_INDEX; - } - } - - @Override - public void onTabClosing(long time, int id) { - super.onTabClosing(time, id); - // Just in case we closed the last tab of a stack we need to trigger the overlap animation. - startMarginAnimation(true); - // Animate the stack to leave incognito mode. - if (!mStacks.get(INCOGNITO_STACK_INDEX).isDisplayable()) onTabModelSwitched(false); - } - - @Override - public void onTabsAllClosing(long time, boolean incognito) { - super.onTabsAllClosing(time, incognito); - getTabStackAtIndex(incognito ? INCOGNITO_STACK_INDEX : NORMAL_STACK_INDEX) - .tabsAllClosingEffect(time); - // trigger the overlap animation. - startMarginAnimation(true); - // Animate the stack to leave incognito mode. - if (!mStacks.get(INCOGNITO_STACK_INDEX).isDisplayable()) onTabModelSwitched(false); - } - - @Override - public void onTabClosureCancelled(long time, int id, boolean incognito) { - super.onTabClosureCancelled(time, id, incognito); - getTabStackAtIndex(incognito ? INCOGNITO_STACK_INDEX : NORMAL_STACK_INDEX) - .undoClosure(time, id); - } - - @Override - public void onTabCreated(long time, int id, int tabIndex, int sourceId, boolean newIsIncognito, - boolean background, float originX, float originY) { - super.onTabCreated( - time, id, tabIndex, sourceId, newIsIncognito, background, originX, originY); - onTabModelSwitched(newIsIncognito); - } - - @Override - public void onTabModelSwitched(boolean toIncognitoTabModel) { - flingStacks(toIncognitoTabModel ? INCOGNITO_STACK_INDEX : NORMAL_STACK_INDEX); - mFlingFromModelChange = true; - } - - @Override - protected void onAnimationFinished() { - super.onAnimationFinished(); - mFlingFromModelChange = false; - if (mTemporarySelectedStack != INVALID_STACK_INDEX) { - mTabModelSelector.selectModel(mTemporarySelectedStack == INCOGNITO_STACK_INDEX); - mTemporarySelectedStack = INVALID_STACK_INDEX; - } - } - - @Override - protected int getMinRenderedScrollOffset() { - // If there's at least one incognito tab open, or we're in the process of switching back - // from incognito to normal mode, return -1 so we don't cause any clamping. Otherwise, - // return 0 to prevent scrolling. - if (mStacks.get(INCOGNITO_STACK_INDEX).isDisplayable() || mFlingFromModelChange) return -1; - return 0; - } - - @Override - public void uiRequestingCloseTab(long time, int id) { - super.uiRequestingCloseTab(time, id); - - int incognitoCount = mTabModelSelector.getModel(true).getCount(); - TabModel model = mTabModelSelector.getModelForTabId(id); - if (model != null && model.isIncognito()) incognitoCount--; - boolean incognitoVisible = incognitoCount > 0; - - // Make sure we show/hide both stacks depending on which tab we're closing. - startMarginAnimation(true, incognitoVisible); - if (!incognitoVisible) onTabModelSwitched(false); - } - - @Override - protected @SwipeMode int computeInputMode(long time, float x, float y, float dx, float dy) { - if (mStacks.size() == 2 && !mStacks.get(1).isDisplayable()) return SwipeMode.SEND_TO_STACK; - return super.computeInputMode(time, x, y, dx, dy); - } - - @Override - public void setActiveStackState(int stackIndex) { - if (stackIndex != getTabStackIndex(Tab.INVALID_TAB_ID)) { - if (stackIndex == NORMAL_STACK_INDEX) { - RecordUserAction.record("MobileStackViewNormalMode"); - } else { - RecordUserAction.record("MobileStackViewIncognitoMode"); - } - } - - super.setActiveStackState(stackIndex); - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java deleted file mode 100644 index 8f59bd37a6b7..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java +++ /dev/null @@ -1,1709 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.graphics.Rect; -import android.graphics.RectF; -import android.os.SystemClock; -import android.util.Pair; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.widget.FrameLayout; - -import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.Callback; -import org.chromium.base.MathUtils; -import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.base.supplier.ObservableSupplier; -import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; -import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils; -import org.chromium.chrome.browser.compositor.LayerTitleCache; -import org.chromium.chrome.browser.compositor.layouts.Layout; -import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; -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.layouts.eventfilter.GestureEventFilter; -import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureHandler; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.OverlappingStack; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.Stack; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackTab; -import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer; -import org.chromium.chrome.browser.homepage.HomepageManager; -import org.chromium.chrome.browser.layouts.EventFilter; -import org.chromium.chrome.browser.layouts.LayoutType; -import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; -import org.chromium.chrome.browser.layouts.animation.FloatProperty; -import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer; -import org.chromium.chrome.browser.tab.Tab; -import org.chromium.chrome.browser.tabmodel.TabList; -import org.chromium.chrome.browser.tabmodel.TabModelSelector; -import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver; -import org.chromium.chrome.browser.tabmodel.TabModelUtils; -import org.chromium.ui.base.LocalizationUtils; -import org.chromium.ui.resources.ResourceManager; - -import java.io.Serializable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -/** - * Base class for layouts that show one or more stacks of tabs. - */ -public abstract class StackLayoutBase extends Layout { - private static final FloatProperty INNER_MARGIN_PERCENT = - new FloatProperty("INNER_MARGIN_PERCENT") { - @Override - public void setValue(StackLayoutBase layoutBase, float v) { - layoutBase.mInnerMarginPercent = v; - } - - @Override - public Float get(StackLayoutBase layoutBase) { - return layoutBase.mInnerMarginPercent; - } - }; - - private static final FloatProperty STACK_OFFSET_Y_PERCENT = - new FloatProperty("STACK_OFFSET_Y_PERCENT") { - @Override - public void setValue(StackLayoutBase layoutBase, float v) { - layoutBase.mStackOffsetYPercent = v; - } - - @Override - public Float get(StackLayoutBase layoutBase) { - return layoutBase.mStackOffsetYPercent; - } - }; - - private static final FloatProperty STACK_SNAP = - new FloatProperty("STACK_SNAP") { - @Override - public void setValue(StackLayoutBase layoutBase, float v) { - layoutBase.setStackSnap(v); - } - - @Override - public Float get(StackLayoutBase layoutBase) { - return layoutBase.mRenderedScrollOffset == layoutBase.mScrollIndexOffset - ? layoutBase.mRenderedScrollOffset - : null; - } - }; - - @IntDef({DragDirection.NONE, DragDirection.HORIZONTAL, DragDirection.VERTICAL}) - @Retention(RetentionPolicy.SOURCE) - public @interface DragDirection { - int NONE = 0; - int HORIZONTAL = 1; - int VERTICAL = 2; - } - - @IntDef({SwipeMode.NONE, SwipeMode.SEND_TO_STACK, SwipeMode.SWITCH_STACK}) - @Retention(RetentionPolicy.SOURCE) - public @interface SwipeMode { - int NONE = 0; - int SEND_TO_STACK = 1; - int SWITCH_STACK = 2; - } - - protected static final int INVALID_STACK_INDEX = -1; - - private static final String TAG = "StackLayoutBase"; - // Width of the partially shown stack when there are multiple stacks. - private static final int MIN_INNER_MARGIN_PERCENT_DP = 55; - private static final float INNER_MARGIN_PERCENT_PERCENT = 0.17f; - - // Speed of the automatic fling in dp/ms - private static final float FLING_SPEED_DP = 1.5f; // dp / ms - private static final int FLING_MIN_DURATION = 100; // ms - - private static final float THRESHOLD_TO_SWITCH_STACK = 0.4f; - - /** - * The delta time applied on the velocity from the fling. This is to compute the kick to help - * switching the stack. - */ - private static final float SWITCH_STACK_FLING_DT = 1.0f / 30.0f; - - /** - * True if this is currently the active layout and startHiding() has not yet been called, false - * otherwise. - */ - protected boolean mIsActiveLayout; - - /** - * This is true if a new tab was just created and we're in the process of hiding this layout as - * a result and false otherwise. - */ - private boolean mIsHidingBecauseOfNewTabCreation; - - /** The list of potentially visible stacks. */ - protected final ArrayList mStacks; - - /** Rectangles that defines the area where each stack need to be laid out. */ - protected final ArrayList mStackRects; - - private final float mDpToPx; - - private int mStackAnimationCount; - - private float mFlingSpeed; // pixel/ms - - private boolean mClicked; - - // If not overscroll, then mRenderedScrollIndex == mScrollIndex; - // Otherwise, mRenderedScrollIndex is updated with the actual index passed in - // from the event handler; and mRenderedScrollIndex is the value we get - // after map mScrollIndex through a decelerate function. - // Here we use float as index so we can smoothly animate the transition between stack. - protected float mRenderedScrollOffset; - private float mScrollIndexOffset; - - private final int mMinMaxInnerMargin; - private float mInnerMarginPercent; - private float mStackOffsetYPercent; - - @DragDirection - private int mDragDirection = DragDirection.NONE; - - @SwipeMode - private int mInputMode = SwipeMode.NONE; - private float mLastOnDownX; - private float mLastOnDownY; - private long mLastOnDownTimeStamp; - - private float mWidth; - private float mHeight; - private @Orientation int mOrientation; - - // Pre-allocated temporary arrays that store id of visible tabs. - // They can be used to call populatePriorityVisibilityList. - // We use StackTab[] instead of ArrayList because the sorting function does - // an allocation to iterate over the elements. - // Do not use out of the context of {@link #updateTabPriority}. - private StackTab[] mSortedPriorityArray; - - private final ArrayList mVisibilityArray = new ArrayList(); - private final VisibilityComparator mVisibilityComparator = new VisibilityComparator(); - private final OrderComparator mOrderComparator = new OrderComparator(); - private Comparator mSortingComparator = mVisibilityComparator; - - private static final int LAYOUTTAB_ASYNCHRONOUS_INITIALIZATION_BATCH_SIZE = 4; - private boolean mDelayedLayoutTabInitRequired; - - /** Which model (normal or incognito) was active when StackLayout was shown. */ - private int mModelIndexWhenOpened; - - /** ID of the tab that was active when this layout was shown. */ - private int mCurrentTabIdWhenOpened; - - /** - * Temporarily stores the index of the selected tab stack. This is used to set the currently - * selected stack in TabModelSelector once the stack-switching animation finishes. - */ - protected int mTemporarySelectedStack = INVALID_STACK_INDEX; - - // Orientation Variables - private PortraitViewport mCachedPortraitViewport; - private PortraitViewport mCachedLandscapeViewport; - - private final ViewGroup mViewContainer; - - private final GestureEventFilter mGestureEventFilter; - - private StackLayoutGestureHandler mGestureHandler; - - private final ArrayList> mLayoutAnimations = - new ArrayList<>(); - - private final ObservableSupplier mBrowserControlsSupplier; - private final BrowserControlsStateProvider.Observer mBrowserControlsObserver; - private Callback mBrowserControlsSupplierObserver; - private TabListSceneLayer mSceneLayer; - private boolean mShowPending; - - private boolean mUiDoneEnteringStack; - - private class StackLayoutGestureHandler implements GestureHandler { - @Override - public void onDown(float x, float y, boolean fromMouse, int buttons) { - long time = time(); - mDragDirection = DragDirection.NONE; - mLastOnDownX = x; - mLastOnDownY = y; - mLastOnDownTimeStamp = time; - - if (shouldIgnoreTouchInput()) return; - mStacks.get(getTabStackIndex()).onDown(time); - } - - @Override - public void onUpOrCancel() { - onUpOrCancel(time()); - } - - @Override - public void drag(float x, float y, float dx, float dy, float tx, float ty) { - if (shouldIgnoreTouchInput()) return; - - @SwipeMode - int oldInputMode = mInputMode; - long time = time(); - float amountX = dx; - float amountY = dy; - mInputMode = computeInputMode(time, x, y, amountX, amountY); - - if (mDragDirection == DragDirection.HORIZONTAL) amountY = 0; - if (mDragDirection == DragDirection.VERTICAL) amountX = 0; - - if (oldInputMode == SwipeMode.SEND_TO_STACK && mInputMode == SwipeMode.SWITCH_STACK) { - mStacks.get(getTabStackIndex()).onUpOrCancel(time); - } else if (oldInputMode == SwipeMode.SWITCH_STACK - && mInputMode == SwipeMode.SEND_TO_STACK) { - onUpOrCancel(time); - } - - if (mInputMode == SwipeMode.SEND_TO_STACK) { - mStacks.get(getTabStackIndex()).drag(time, x, y, amountX, amountY); - } else if (mInputMode == SwipeMode.SWITCH_STACK) { - scrollStacks(isUsingHorizontalLayout() ? amountY : amountX); - } - } - - @Override - public void click(float x, float y, boolean fromMouse, int buttons) { - if (shouldIgnoreTouchInput()) return; - - // Click event happens before the up event. mClicked is set to mute the up event. - mClicked = true; - PortraitViewport viewportParams = getViewportParameters(); - final int stackIndexDeltaAt = viewportParams.getStackIndexDeltaAt(x, y); - if (stackIndexDeltaAt == 0) { - mStacks.get(getTabStackIndex()).click(time(), x, y); - } else { - final int newStackIndex = getTabStackIndex() + stackIndexDeltaAt; - if (newStackIndex < 0 || newStackIndex >= mStacks.size()) return; - if (!mStacks.get(newStackIndex).isDisplayable()) return; - flingStacks(newStackIndex); - } - requestUpdate(); - } - - @Override - public void fling(float x, float y, float velocityX, float velocityY) { - if (shouldIgnoreTouchInput()) return; - - long time = time(); - float vx = velocityX; - float vy = velocityY; - - if (mInputMode == SwipeMode.NONE) { - mInputMode = computeInputMode( - time, x, y, vx * SWITCH_STACK_FLING_DT, vy * SWITCH_STACK_FLING_DT); - } - - if (mInputMode == SwipeMode.SEND_TO_STACK) { - mStacks.get(getTabStackIndex()).fling(time, x, y, vx, vy); - } else if (mInputMode == SwipeMode.SWITCH_STACK) { - final float velocity = isUsingHorizontalLayout() ? vy : vx; - final float origin = isUsingHorizontalLayout() ? y : x; - final float max = isUsingHorizontalLayout() ? getHeight() : getWidth(); - final float predicted = origin + velocity * SWITCH_STACK_FLING_DT; - final float delta = MathUtils.clamp(predicted, 0, max) - origin; - scrollStacks(delta); - } - requestUpdate(); - } - - @Override - public void onLongPress(float x, float y) { - if (shouldIgnoreTouchInput()) return; - mStacks.get(getTabStackIndex()).onLongPress(time(), x, y); - } - - @Override - public void onPinch(float x0, float y0, float x1, float y1, boolean firstEvent) { - if (shouldIgnoreTouchInput()) return; - mStacks.get(getTabStackIndex()).onPinch(time(), x0, y0, x1, y1, firstEvent); - } - - private void onUpOrCancel(long time) { - if (shouldIgnoreTouchInput()) return; - cancelDragTabs(time); - } - - private void cancelDragTabs(long time) { - int currentIndex = getTabStackIndex(); - if (!mClicked - && Math.abs(currentIndex + mRenderedScrollOffset) > THRESHOLD_TO_SWITCH_STACK) { - int nextIndex; - if (currentIndex + mRenderedScrollOffset < 0) { - nextIndex = currentIndex + 1; - } else { - nextIndex = currentIndex - 1; - } - if (mStacks.get(nextIndex).isDisplayable()) { - setActiveStackState(nextIndex); - } - } - - mClicked = false; - finishScrollStacks(); - mStacks.get(getTabStackIndex()).onUpOrCancel(time); - mInputMode = SwipeMode.NONE; - } - - private long time() { - return LayoutManagerImpl.time(); - } - } - - /** - * @param context The current Android's context. - * @param updateHost The {@link LayoutUpdateHost} view for this - * layout. - * @param renderHost The {@link LayoutRenderHost} view for this - * layout. - * @param browserControlsStateProviderSupplier An {@link ObservableSupplier} for the - * {@link BrowserControlsStateProvider}. - */ - public StackLayoutBase(Context context, LayoutUpdateHost updateHost, - LayoutRenderHost renderHost, - ObservableSupplier browserControlsStateProviderSupplier) { - super(context, updateHost, renderHost); - - mGestureHandler = new StackLayoutGestureHandler(); - mGestureEventFilter = new GestureEventFilter(context, mGestureHandler); - - mMinMaxInnerMargin = (int) (MIN_INNER_MARGIN_PERCENT_DP + 0.5); - mFlingSpeed = FLING_SPEED_DP; - - mStacks = new ArrayList(); - mStackRects = new ArrayList(); - mViewContainer = new FrameLayout(getContext()); - - mDpToPx = context.getResources().getDisplayMetrics().density; - mBrowserControlsSupplier = browserControlsStateProviderSupplier; - mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() { - @Override - public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset, - int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) { - if (!isActive()) return; - - notifySizeChanged(mWidth, mHeight, mOrientation); - } - }; - - // TODO(https://crbug.com/1084528): Replace with OneShotSupplier when it is available. - mBrowserControlsSupplierObserver = (browserControlsStateProvider) - -> browserControlsStateProvider.addObserver(mBrowserControlsObserver); - mBrowserControlsSupplier.addObserver(mBrowserControlsSupplierObserver); - } - - public void initWithNative() { - ensureSceneLayerCreated(); - if (mShowPending) { - mShowPending = false; - show(LayoutManagerImpl.time(), false); - } - } - - @Override - public void destroy() { - if (mBrowserControlsSupplier != null) { - mBrowserControlsSupplier.removeObserver(mBrowserControlsSupplierObserver); - - if (mBrowserControlsSupplier.get() != null) { - mBrowserControlsSupplier.get().removeObserver(mBrowserControlsObserver); - } - } - - super.destroy(); - } - - /** - * Sets the stack stap value. - * - * @param v Value to set. - */ - private void setStackSnap(float v) { - mRenderedScrollOffset = v; - mScrollIndexOffset = v; - } - - /** - * Whether or not we're currently having the tabs scroll horizontally (as opposed to - * vertically). - */ - private boolean isUsingHorizontalLayout() { - return getOrientation() == Orientation.LANDSCAPE; - } - - /** - * Updates this layout to show one tab stack for each of the passed-in TabLists. Takes a - * reference to the lists param and expects it not to change. - * @param lists The list of TabLists to use. - */ - protected void setTabLists(List lists) { - if (mStacks.size() > lists.size()) { - mStacks.subList(lists.size(), mStacks.size()).clear(); - } - while (mStacks.size() < lists.size()) { - Stack stack = new OverlappingStack(getContext(), this); - stack.notifySizeChanged(mWidth, mHeight, mOrientation); - mStacks.add(stack); - } - - for (int i = 0; i < lists.size(); i++) { - mStacks.get(i).setTabList(lists.get(i)); - } - - // mStackRects will get updated in updateLayout() - } - - @Override - public boolean forceShowBrowserControlsAndroidView() { - return true; - } - - /** - * A subclass can override this to return true to cause touch input to be ignored during certain - * operations (e.g. animations). - */ - protected boolean shouldIgnoreTouchInput() { - return false; - } - - /** - * Simulates a click on the view at the specified pixel offset - * from the top left of the view. - * This is used by UI tests. - * @param x Coordinate of the click in dp. - * @param y Coordinate of the click in dp. - */ - @VisibleForTesting - public void simulateClick(float x, float y) { - mGestureHandler.click(x, y, false, -1); - } - - /** - * Simulates a drag and issues Up-event to commit the drag. - * @param x Coordinate to start the Drag from in dp. - * @param y Coordinate to start the Drag from in dp. - * @param dX Amount of drag in X direction in dp. - * @param dY Amount of drag in Y direction in dp. - */ - @VisibleForTesting - public void simulateDrag(float x, float y, float dX, float dY) { - mGestureHandler.onDown(x, y, false, -1); - mGestureHandler.drag(x, y, dX, dY, -1, -1); - mGestureHandler.onUpOrCancel(); - } - - @Override - public @ViewportMode int getViewportMode() { - return ViewportMode.ALWAYS_FULLSCREEN; - } - - @Override - public void setTabModelSelector(TabModelSelector modelSelector, TabContentManager manager) { - super.setTabModelSelector(modelSelector, manager); - if (mSceneLayer != null) { - mSceneLayer.setTabModelSelector(modelSelector); - } - resetScrollData(); - - new TabModelSelectorTabModelObserver(mTabModelSelector) { - @Override - public void tabClosureUndone(Tab tab) { - if (!isActive()) return; - onTabClosureCancelled(LayoutManagerImpl.time(), tab.getId(), tab.isIncognito()); - } - }; - } - - /** - * Called when a tab close has been undone and the tab has been restored. - * @param time The current time of the app in ms. - * @param id The id of the Tab. - * @param incognito True if the tab is incognito - */ - public void onTabClosureCancelled(long time, int id, boolean incognito) {} - - /** - * Get the tab stack at the specified index. - * - * @param index Which stack should be returned. - * @return The stack at the specified index. - * @VisibleForTesting - */ - public Stack getTabStackAtIndex(int index) { - return mStacks.get(index); - } - - /** - * Get the tab stack state. - * @return The tab stack index for the given tab id. - */ - private int getTabStackIndex() { - return getTabStackIndex(Tab.INVALID_TAB_ID); - } - - /** - * Get the tab stack state for the specified tab id. - * - * @param tabId The id of the tab to lookup. - * @return The tab stack index for the given tab id. - * @VisibleForTesting - */ - protected abstract int getTabStackIndex(int tabId); - - /** - * Get the tab stack state for the specified tab id. - * - * @param tabId The id of the tab to lookup. - * @return The tab stack state for the given tab id. - * @VisibleForTesting - */ - protected Stack getTabStackForTabId(int tabId) { - return mStacks.get(getTabStackIndex(tabId)); - } - - /** - * Commits outstanding model states. - * @param time The current time of the app in ms. - */ - public void commitOutstandingModelState(long time) { - for (int i = 0; i < mStacks.size(); i++) { - mStacks.get(i).ensureCleaningUpDyingTabs(time); - } - } - - @Override - public void onTabSelecting(long time, int tabId) { - // We update TabModelSelector's current model when incognito mode is toggled in the tab - // switcher. So the "current model index" is already the one that we're leaving active when - // the tab switcher is closed. - final int newModelIndex = mTabModelSelector.getCurrentModelIndex(); - if (newModelIndex != mModelIndexWhenOpened) { - final int indexInNewModel = mTabModelSelector.getCurrentModel().index(); - if (indexInNewModel == mTabModelSelector.getCurrentModel().index()) { - // TabModelImpl logs this action when we switch to a different index within a - // TabModelImpl. If we switch between TabModelImpls (i.e. switch between normal and - // incognito mode), but leave the index the same (i.e. switch back to the most - // recently active tab in that stack), TabModelImpl doesn't catch that case, so we - // log it here. - RecordUserAction.record("MobileTabSwitched"); - } - } - - commitOutstandingModelState(time); - if (tabId == Tab.INVALID_TAB_ID) tabId = mTabModelSelector.getCurrentTabId(); - super.onTabSelecting(time, tabId); - mStacks.get(getTabStackIndex()).tabSelectingEffect(time, tabId); - startMarginAnimation(false); - startYOffsetAnimation(false); - finishScrollStacks(); - } - - @Override - public void onTabClosing(long time, int id) { - Stack stack = getTabStackForTabId(id); - if (stack == null) return; - stack.tabClosingEffect(time, id); - } - - @Override - public boolean handlesCloseAll() { - return true; - } - - @Override - public boolean handlesTabCreating() { - return true; - } - - @Override - public boolean handlesTabClosing() { - return true; - } - - @Override - public void attachViews(ViewGroup container) { - // TODO(dtrainor): This is a hack. We're attaching to the parent of the view container - // which is the content container of the Activity. - ((ViewGroup) container.getParent()) - .addView(mViewContainer, - new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - } - - @Override - public void detachViews() { - if (mViewContainer.getParent() != null) { - ((ViewGroup) mViewContainer.getParent()).removeView(mViewContainer); - } - mViewContainer.removeAllViews(); - } - - /** - * @return A {@link ViewGroup} that {@link Stack}s can use to interact with the Android view - * hierarchy. - */ - public ViewGroup getViewContainer() { - return mViewContainer; - } - - @Override - public boolean onBackPressed() { - // Force any in progress animations to end. This was introduced because - // we end up with 0 tabs if the animation for all tabs closing is still - // running when the back button is pressed. We should finish the animation - // and close Chrome instead. - // See http://crbug.com/522447 - onUpdateAnimation(SystemClock.currentThreadTimeMillis(), true); - return false; - } - - @Override - public void onTabCreating(int sourceTabId) { - // Force any in progress animations to end. This was introduced because - // we end up with 0 tabs if the animation for all tabs closing is still - // running when a new tab is created. - // See http://crbug.com/496557 - onUpdateAnimation(SystemClock.currentThreadTimeMillis(), true); - } - - @Override - public void onTabCreated(long time, int id, int tabIndex, int sourceId, boolean newIsIncognito, - boolean background, float originX, float originY) { - super.onTabCreated( - time, id, tabIndex, sourceId, newIsIncognito, background, originX, originY); - - // Suppress startHiding()'s logging to the Tabs.TabOffsetOfSwitch histogram. - mIsHidingBecauseOfNewTabCreation = true; - startHiding(id, false); - mStacks.get(getTabStackIndex(id)).tabCreated(time, id); - - startMarginAnimation(false); - } - - // This method is called if the following sequence of operations occurs: - // 1. Enter multi-window mode - // 2. Create a second Chrome instance by moving a tab to the other window - // 3. In the top window, enter the tab switcher - // 4. Expand the top window to full screen. - @Override - public void onTabRestored(long time, int tabId) { - super.onTabRestored(time, tabId); - // Call show() so that new stack tabs and potentially new stacks get created. - // TODO(twellington): add animation for showing the restored tab. - if (mSceneLayer == null) { - mShowPending = true; - return; - } - show(time, false); - } - - @Override - public boolean onUpdateAnimation(long time, boolean jumpToEnd) { - boolean animationsWasDone = true; - if (!mLayoutAnimations.isEmpty()) { - if (jumpToEnd) { - forceAnimationToFinish(); - } else { - animationsWasDone = !isLayoutAnimating(); - } - if (animationsWasDone || jumpToEnd) { - onAnimationFinished(); - } - } - - boolean finishedAllViews = true; - for (int i = 0; i < mStacks.size(); i++) { - finishedAllViews &= mStacks.get(i).onUpdateViewAnimation(time, jumpToEnd); - } - - boolean finishedAllCompositors = true; - for (int i = 0; i < mStacks.size(); i++) { - finishedAllCompositors &= mStacks.get(i).onUpdateCompositorAnimations(time, jumpToEnd); - } - - if (animationsWasDone && finishedAllViews && finishedAllCompositors) { - return true; - } else { - if (!animationsWasDone || !finishedAllCompositors) requestUpdate(); - return false; - } - } - - /** - * Called when layout-specific actions are needed after the animation finishes. - */ - protected void onAnimationStarted() {} - - /** - * Called when layout-specific actions are needed after the animation finishes. - */ - protected void onAnimationFinished() {} - - /** - * Called when a UI element is attempting to select a tab. This will perform the animation - * and then actually propagate the action. This starts hiding this layout which, when complete, - * will actually select the tab. - * @param time The current time of the app in ms. - * @param id The id of the tab to select. - */ - public void uiSelectingTab(long time, int id) { - onTabSelecting(time, id); - } - - /** - * Called when a UI element is attempting to close a tab. This will perform the required close - * animations. When the UI is ready to actually close the tab - * {@link #uiDoneClosingTab(long, int, boolean, boolean)} should be called to actually propagate - * the event to the model. - * @param time The current time of the app in ms. - * @param id The id of the tab to close. - */ - public void uiRequestingCloseTab(long time, int id) { - // Start the tab closing effect if necessary. - getTabStackForTabId(id).tabClosingEffect(time, id); - } - - /** - * Called when a UI element is done animating the close tab effect started by - * {@link #uiRequestingCloseTab(long, int)}. This actually pushes the close event to the model. - * @param time The current time of the app in ms. - * @param id The id of the tab to close. - * @param canUndo Whether or not this close can be undone. - * @param incognito Whether or not this was for the incognito stack or not. - */ - public void uiDoneClosingTab( - final long time, final int id, boolean canUndo, final boolean incognito) { - // If there are any ongoing layout animations, postpone this until they are done since - // closeTabById does a lot of work. - for (int i = 0; i < mLayoutAnimations.size(); i++) { - if (mLayoutAnimations.get(i).first.isRunning()) { - final boolean cachedCanUndo = canUndo; - final AnimatorListenerAdapter adapter = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - uiDoneClosingTab(time, id, cachedCanUndo, incognito); - animation.removeListener(this); - } - }; - mLayoutAnimations.get(i).first.addListener(adapter); - return; - } - } - - assert !isLayoutAnimating(); - - // If homepage is enabled and there is a maximum of 1 tab in both models - // (this is the last tab), the tab closure cannot be undone. - canUndo &= !(HomepageManager.shouldCloseAppWithZeroTabs() - && (mTabModelSelector.getModel(true).getCount() - + mTabModelSelector.getModel(false).getCount() - < 2)); - - // Propagate the tab closure to the model. - TabModelUtils.closeTabById(mTabModelSelector.getModel(incognito), id, canUndo); - } - - public void uiDoneClosingAllTabs(boolean incognito) { - // Propagate the tab closure to the model. - mTabModelSelector.getModel(incognito).closeAllTabs(false, false); - } - - /** - * Called when a {@link Stack} instance is done animating the stack enter effect. - */ - public void uiDoneEnteringStack() { - mUiDoneEnteringStack = true; - // Tabs don't overlap in the horizontal tab switcher experiment, so the order comparator - // already does what we want (the visibility comparator's logic actually doesn't compute - // visibility properly in this case). - mSortingComparator = mVisibilityComparator; - doneShowing(); - } - - /** - * Starts the animation for the opposite stack to slide in or out when entering - * or leaving stack view. The animation should be super fast to match more or less - * the fling animation. - * @param enter True if the stack view is being entered, false if the stack view - * is being left. - */ - protected void startMarginAnimation(boolean enter) { - startMarginAnimation(enter, mStacks.size() >= 2 && mStacks.get(1).isDisplayable()); - } - - protected void startMarginAnimation(boolean enter, boolean showMargin) { - // Any outstanding animations must be cancelled to avoid race condition. - cancelAnimation(INNER_MARGIN_PERCENT); - - float start = mInnerMarginPercent; - float end = enter && showMargin ? 1.0f : 0.0f; - if (start != end) { - addToAnimation(INNER_MARGIN_PERCENT, start, end, 200, 0); - } - } - - private void startYOffsetAnimation(boolean enter) { - // Any outstanding animations must be cancelled to avoid race condition. - cancelAnimation(STACK_OFFSET_Y_PERCENT); - - float start = mStackOffsetYPercent; - float end = enter ? 1.f : 0.f; - if (start != end) { - addToAnimation(STACK_OFFSET_Y_PERCENT, start, end, 300, 0); - } - } - - @Override - public void show(long time, boolean animate) { - super.show(time, animate); - mUiDoneEnteringStack = false; - - if (!mIsActiveLayout) { - // The mIsActiveLayout check is necessary because there are certain edge cases where - // show() is called (e.g. to refresh the Stacks) while the tab switcher is already - // showing. - - // Note: there are some edge cases (e.g. the last open tab is closed somehow while the - // tab switcher is not open) that can also cause this event to be logged without a - // toolbar interaction. The event name contains "Toolbar" for historical reasons; the - // current intent is to log whenever the tab switcher is entered. - RecordUserAction.record("MobileToolbarShowStackView"); - - mModelIndexWhenOpened = mTabModelSelector.getCurrentModelIndex(); - mCurrentTabIdWhenOpened = mTabModelSelector.getCurrentTabId(); - } - mIsActiveLayout = true; - - Tab tab = mTabModelSelector.getCurrentTab(); - if (tab != null && tab.isNativePage()) mTabContentManager.cacheTabThumbnail(tab); - - // Remove any views in case we're getting another call to show before we hide (quickly - // toggling the tab switcher button). - mViewContainer.removeAllViews(); - int currentTabStack = getTabStackIndex(); - - for (int i = mStacks.size() - 1; i >= 0; --i) { - mStacks.get(i).reset(); - if (mStacks.get(i).isDisplayable()) { - mStacks.get(i).show(i == currentTabStack); - } else { - mStacks.get(i).cleanupTabs(); - } - } - // Initialize the animation and the positioning of all the elements - mSortingComparator = mOrderComparator; - resetScrollData(); - for (int i = mStacks.size() - 1; i >= 0; --i) { - if (mStacks.get(i).isDisplayable()) { - boolean offscreen = (i != getTabStackIndex()); - mStacks.get(i).stackEntered(time, !offscreen); - } - } - startMarginAnimation(true); - startYOffsetAnimation(true); - flingStacks(getTabStackIndex()); - - if (!animate) onUpdateAnimation(time, true); - - // We will render before we get a call to updateLayout. Need to make sure all of the tabs - // we need to render are up to date. - updateLayout(time, 0); - } - - @Override - public void doneShowing() { - if (!mUiDoneEnteringStack) return; - - super.doneShowing(); - - if (mBrowserControlsSupplier.get() != null) { - mBrowserControlsSupplier.get().addObserver(mBrowserControlsObserver); - notifySizeChanged(mWidth, mHeight, mOrientation); - } - } - - @Override - public void notifySizeChanged(float width, float height, @Orientation int orientation) { - mWidth = width; - mHeight = height; - mOrientation = orientation; - mCachedLandscapeViewport = null; - mCachedPortraitViewport = null; - for (Stack stack : mStacks) { - stack.notifySizeChanged(width, height, orientation); - } - resetScrollData(); - requestUpdate(); - } - - @Override - public void contextChanged(Context context) { - super.contextChanged(context); - StackTab.resetDimensionConstants(context); - for (Stack stack : mStacks) { - stack.contextChanged(context); - } - requestUpdate(); - } - - protected int getMinRenderedScrollOffset() { - return -(mStacks.size() - 1); - } - - /** - * Computes the input mode for drag and fling based on the first event position. - * @param time The current time of the app in ms. - * @param x The x layout position of the mouse (without the displacement). - * @param y The y layout position of the mouse (without the displacement). - * @param dx The x displacement happening this frame. - * @param dy The y displacement happening this frame. - * @return The input mode to select. - */ - protected @SwipeMode int computeInputMode(long time, float x, float y, float dx, float dy) { - if (mStacks.size() == 0) return SwipeMode.NONE; - if (mStacks.size() == 1) return SwipeMode.SEND_TO_STACK; - - int currentIndex = getTabStackIndex(); - - // When a drag starts, lock the drag into being either horizontal or vertical until the - // next touch down. The deltas here are already verified by StackLayoutGestureHandler as - // being above some threshold so that we know we're handling a drag or fling and not a long - // press. - if (mDragDirection == DragDirection.NONE) { - if (Math.abs(dx) > Math.abs(dy)) { - mDragDirection = DragDirection.HORIZONTAL; - } else { - mDragDirection = DragDirection.VERTICAL; - } - } - - if ((mDragDirection == DragDirection.VERTICAL) ^ isUsingHorizontalLayout()) { - return SwipeMode.SEND_TO_STACK; - } - - float relativeX = mLastOnDownX - (x + dx); - float relativeY = mLastOnDownY - (y + dy); - float switchDelta = isUsingHorizontalLayout() ? relativeY : relativeX; - - // In LTR portrait mode, the first stack can be swiped to the left to switch to the second - // stack, and the last stack can be swiped to the right to switch to the first stack. We - // reverse the check for RTL portrait mode because increasing the stack index corresponds - // to a negative switchDelta. If there are more than two stacks, we do not currently support - // swiping to close on any of the stacks in the middle - // - // Landscape mode is like LTR portrait mode (increasing the stack index corresponds to a - // positive switchDelta). - final boolean isRtlPortraitMode = - (!isUsingHorizontalLayout() && LocalizationUtils.isLayoutRtl()); - final boolean onLeftmostStack = (currentIndex == 0 && !isRtlPortraitMode) - || (currentIndex == mStacks.size() - 1 && isRtlPortraitMode); - final boolean onRightmostStack = (currentIndex == 0 && isRtlPortraitMode) - || (currentIndex == mStacks.size() - 1 && !isRtlPortraitMode); - if ((onLeftmostStack && switchDelta < 0) || (onRightmostStack && switchDelta > 0)) { - // Dragging in a direction the stack cannot switch. Pass the drag to the Stack, which - // will treat it as intending to discard a tab. - return SwipeMode.SEND_TO_STACK; - } else { - // Interpret the drag as intending to switch between tab stacks. - return SwipeMode.SWITCH_STACK; - } - } - - class PortraitViewport { - protected float mWidth; - protected float mHeight; - PortraitViewport() { - mWidth = StackLayoutBase.this.getWidth(); - mHeight = StackLayoutBase.this.getHeightMinusContentOffsetsDp(); - } - - float getClampedRenderedScrollOffset() { - return MathUtils.clamp(mRenderedScrollOffset, 0, getMinRenderedScrollOffset()); - } - - float getInnerMargin() { - float margin = mInnerMarginPercent - * Math.max(mMinMaxInnerMargin, mWidth * INNER_MARGIN_PERCENT_PERCENT); - return margin; - } - - /** - * Returns an offset that can be added to the index of the current stack to get the index of - * the stack at the specified on-screen location. - * @param x The x coordinate of the specified on-screen location. - * @param y The x coordinate of the specified on-screen location. - * @return The offset to be added to the index of the current stack. - */ - int getStackIndexDeltaAt(float x, float y) { - int delta = 0; - if (x < getCurrentStackLeft()) { - delta = -1; - } else if (x > getCurrentStackLeft() + getWidth()) { - delta = 1; - } - - // Tabs are counted from left to right in LTR mode, but from right to left in RTL mode. - if (LocalizationUtils.isLayoutRtl()) delta *= -1; - - return delta; - } - - /** - * @return The current x coordinate for the left edge of the first stack (right edge if in - * RTL mode). - */ - float getStack0Left() { - float stack0LeftLtr = getClampedRenderedScrollOffset() * getFullScrollDistance(); - if (mStacks.size() > 2) { - // If we have one or two stacks, we only show a margin on the right side of the left - // stack and on the left side of the right stack. But if we have three or more - // stacks, we put a margin on both sides - stack0LeftLtr += getInnerMargin() / 2; - } - - if (LocalizationUtils.isLayoutRtl()) return getInnerMargin() - stack0LeftLtr; - - return stack0LeftLtr; - } - - /** - * @return The current x coordinate for the left edge of the current stack (actually the - * right edge if in RTL mode). - */ - float getCurrentStackLeft() { - float offset = getClampedRenderedScrollOffset() + getTabStackIndex(); - if (mStacks.size() > 2) { - return offset * getFullScrollDistance() + getInnerMargin() / 2; - } - - // Note: getInnerMargin() is zero if there's only one stack. - boolean isRightStack = (getTabStackIndex() == 1) ^ LocalizationUtils.isLayoutRtl(); - return offset * getFullScrollDistance() + (isRightStack ? getInnerMargin() : 0); - } - - float getWidth() { - return mWidth - getInnerMargin(); - } - - float getHeight() { - return mHeight; - } - - float getStack0Top() { - return getTopHeightOffset(); - } - - float getStack0ToStack1TranslationX() { - return Math.round(LocalizationUtils.isLayoutRtl() ? -mWidth + getInnerMargin() - : mWidth - getInnerMargin()); - } - - float getStack0ToStack1TranslationY() { - return 0.0f; - } - - float getTopHeightOffset() { - return getTopContentOffsetDp() * mStackOffsetYPercent; - } - } - - class LandscapeViewport extends PortraitViewport { - LandscapeViewport() { - // This is purposefully inverted. - mWidth = StackLayoutBase.this.getHeightMinusContentOffsetsDp(); - mHeight = StackLayoutBase.this.getWidth(); - } - - @Override - float getInnerMargin() { - float margin = mInnerMarginPercent - * Math.max(mMinMaxInnerMargin, mWidth * INNER_MARGIN_PERCENT_PERCENT); - return margin; - } - - @Override - int getStackIndexDeltaAt(float x, float y) { - if (y < getCurrentStackTop()) return -1; - if (y > getCurrentStackTop() + getHeight()) return 1; - return 0; - } - - @Override - float getStack0Left() { - return 0.f; - } - - @Override - float getStack0Top() { - return getClampedRenderedScrollOffset() * getFullScrollDistance() - + getTopHeightOffset(); - } - - /** - * @return The current y coordinate for the top edge of the current stack. - */ - float getCurrentStackTop() { - float offset = getClampedRenderedScrollOffset() + getTabStackIndex(); - if (mStacks.size() > 2) { - return offset * getFullScrollDistance() + getInnerMargin() / 2 - + getTopHeightOffset(); - } - - return offset * getFullScrollDistance() - + ((getTabStackIndex() == 1) ? getInnerMargin() : 0) + getTopHeightOffset(); - } - - @Override - float getWidth() { - return super.getHeight(); - } - - @Override - float getHeight() { - return super.getWidth(); - } - - @Override - float getStack0ToStack1TranslationX() { - return super.getStack0ToStack1TranslationY(); - } - - @Override - float getStack0ToStack1TranslationY() { - // Need getHeight() for this case instead of getHeightMinusBrowserControls() so the - // normal stack goes up high enough to clear the status bar when the incognito stack is - // active. - return Math.round(mWidth - getInnerMargin()); - } - } - - /** - * @return The height of the drawing area minus the top and bottom content offsets in dp. - */ - public float getHeightMinusContentOffsetsDp() { - return getHeight() - (getTopContentOffsetDp() + getBottomContentOffsetDp()); - } - - /** - * @return The offset of the content from the top of the screen in dp. - */ - public float getTopContentOffsetDp() { - final BrowserControlsStateProvider provider = mBrowserControlsSupplier.get(); - return provider != null ? provider.getContentOffset() / mDpToPx : 0.f; - } - - /** - * @return The offset of the content from the bottom of the screen in dp. - */ - private float getBottomContentOffsetDp() { - final BrowserControlsStateProvider provider = mBrowserControlsSupplier.get(); - return provider != null ? BrowserControlsUtils.getBottomContentOffset(provider) / mDpToPx - : 0.f; - } - - private PortraitViewport getViewportParameters() { - if (isUsingHorizontalLayout()) { - if (mCachedLandscapeViewport == null) { - mCachedLandscapeViewport = new LandscapeViewport(); - } - return mCachedLandscapeViewport; - } else { - if (mCachedPortraitViewport == null) { - mCachedPortraitViewport = new PortraitViewport(); - } - return mCachedPortraitViewport; - } - } - - /** - * Scrolls the tab stacks by amount delta (clamped so that it's not possible to scroll past the - * last stack in either direciton). Positive delta corresponds to increasing the x coordinate - * in portrait mode (in both LTR and RTL modes), or increasing the y coordinate in landscape - * mode. - * @param delta The amount to scroll by. - */ - private void scrollStacks(float delta) { - cancelAnimation(STACK_SNAP); - float fullDistance = getFullScrollDistance(); - mScrollIndexOffset += MathUtils.flipSignIf(delta / fullDistance, - !isUsingHorizontalLayout() && LocalizationUtils.isLayoutRtl()); - mRenderedScrollOffset = - MathUtils.clamp(mScrollIndexOffset, 0, getMinRenderedScrollOffset()); - requestUpdate(); - } - - /** - * Scrolls over to the tab stack at the specified index, and records that it's now the current - * tab stack. - * @param index The index of the newly-selected tab stack. - */ - protected void flingStacks(int index) { - setActiveStackState(index); - finishScrollStacks(); - requestUpdate(); - } - - /** - * Animate to the final position of the stack. Unfortunately, both touch-up - * and fling can be called and this depends on fling always being called last. - * If fling is called first, onUpOrCancel can override the fling position - * with the opposite. For example, if the user does a very small fling from - * incognito to non-incognito, which leaves the up event in the incognito side. - */ - private void finishScrollStacks() { - cancelAnimation(STACK_SNAP); - final int currentModelIndex = getTabStackIndex(); - float delta = Math.abs(currentModelIndex + mRenderedScrollOffset); - float target = -currentModelIndex; - if (delta != 0) { - long duration = FLING_MIN_DURATION - + (long) Math.abs(delta * getFullScrollDistance() / mFlingSpeed); - addToAnimation(STACK_SNAP, mRenderedScrollOffset, target, duration, 0); - } else { - setStackSnap(target); - onAnimationFinished(); - } - } - - /** - * Pushes a rectangle to be drawn on the screen on top of everything. - * - * @param rect The rectangle to be drawn on screen - * @param color The color of the rectangle - */ - public void pushDebugRect(Rect rect, int color) { - if (rect.left > rect.right) { - int tmp = rect.right; - rect.right = rect.left; - rect.left = tmp; - } - if (rect.top > rect.bottom) { - int tmp = rect.bottom; - rect.bottom = rect.top; - rect.top = tmp; - } - mRenderHost.pushDebugRect(rect, color); - } - - @Override - protected void updateLayout(long time, long dt) { - if (mStacks.size() == 0) return; - - super.updateLayout(time, dt); - boolean needUpdate = false; - - if (mStackRects.size() > mStacks.size()) { - mStackRects.subList(mStacks.size(), mStackRects.size()).clear(); - } - while (mStackRects.size() < mStacks.size()) mStackRects.add(new RectF()); - - final PortraitViewport viewport = getViewportParameters(); - - if (!mStackRects.isEmpty()) { - mStackRects.get(0).left = viewport.getStack0Left(); - mStackRects.get(0).right = mStackRects.get(0).left + viewport.getWidth(); - mStackRects.get(0).top = viewport.getStack0Top(); - mStackRects.get(0).bottom = mStackRects.get(0).top + viewport.getHeight(); - } - - for (int i = 1; i < mStackRects.size(); i++) { - mStackRects.get(i).left = - mStackRects.get(i - 1).left + viewport.getStack0ToStack1TranslationX(); - mStackRects.get(i).right = mStackRects.get(i).left + viewport.getWidth(); - mStackRects.get(i).top = - mStackRects.get(i - 1).top + viewport.getStack0ToStack1TranslationY(); - mStackRects.get(i).bottom = mStackRects.get(i).top + viewport.getHeight(); - } - - for (int i = 0; i < mStacks.size(); i++) { - final float scrollDistance = Math.abs(i + mRenderedScrollOffset); - final float stackFocus = MathUtils.clamp(1 - scrollDistance, 0, 1); - - // The overlapping stack only uses the OrderComparator for visibliity prioritization - // during the animation to open the tab switcher. For this case, we pass a fixed index - // for the currently-selected tab. - // - // If the non-overlapping horizontal tab switcher experiment is enabled, we pass -1 so - // NonOverlappingStack can use the scroll position to keep the index used for visibility - // prioritization up-to-date. - final boolean useFixedIndex = mSortingComparator == mOrderComparator; - mStacks.get(i).setStackFocusInfo( - stackFocus, useFixedIndex ? mStacks.get(i).getTabList().index() : -1); - } - - // Compute position and visibility - for (int i = 0; i < mStacks.size(); i++) { - mStacks.get(i).computeTabPosition(time, mStackRects.get(i)); - } - - // Pre-allocate/resize {@link #mLayoutTabs} before it get populated by - // computeTabPositionAndAppendLayoutTabs. - int tabVisibleCount = 0; - for (int i = 0; i < mStacks.size(); i++) { - tabVisibleCount += mStacks.get(i).getVisibleCount(); - } - - if (tabVisibleCount == 0) { - mLayoutTabs = null; - } else if (mLayoutTabs == null || mLayoutTabs.length != tabVisibleCount) { - mLayoutTabs = new LayoutTab[tabVisibleCount]; - } - - int index = 0; - for (int i = 0; i < mStacks.size(); i++) { - // Append tabs for the current stack last so they get priority in rendering. - if (getTabStackIndex() == i) continue; - index = appendVisibleLayoutTabs(time, i, mLayoutTabs, index); - } - index = appendVisibleLayoutTabs(time, getTabStackIndex(), mLayoutTabs, index); - assert index == tabVisibleCount : "index should be incremented up to tabVisibleCount"; - - // Update tab snapping - for (int i = 0; i < tabVisibleCount; i++) { - if (updateSnap(dt, mLayoutTabs[i])) needUpdate = true; - } - - if (needUpdate) requestUpdate(); - - // Since we've updated the positions of the stacks and tabs, let's go ahead and update - // the visible tabs. - updateTabPriority(); - } - - private int appendVisibleLayoutTabs(long time, int stackIndex, LayoutTab[] tabs, int tabIndex) { - final StackTab[] stackTabs = mStacks.get(stackIndex).getTabs(); - if (stackTabs != null) { - for (int i = 0; i < stackTabs.length; i++) { - LayoutTab t = stackTabs[i].getLayoutTab(); - if (t.isVisible()) tabs[tabIndex++] = t; - } - } - return tabIndex; - } - - /** - * Sets the active tab stack. - * - * @param stackIndex Index of the tab stack to be made active. - */ - public void setActiveStackState(int stackIndex) { - mTemporarySelectedStack = stackIndex; - } - - private void resetScrollData() { - mScrollIndexOffset = -getTabStackIndex(); - mRenderedScrollOffset = mScrollIndexOffset; - } - - /** - * @return The distance between two neighboring tab stacks. - */ - private float getFullScrollDistance() { - float distance = isUsingHorizontalLayout() ? getHeightMinusContentOffsetsDp() : getWidth(); - if (mStacks.size() > 2) { - return distance - getViewportParameters().getInnerMargin(); - } - - return distance - 2 * getViewportParameters().getInnerMargin(); - } - - @Override - public void startHiding(int nextTabId, boolean hintAtTabSelection) { - if (mBrowserControlsSupplier.get() != null) { - mBrowserControlsSupplier.get().removeObserver(mBrowserControlsObserver); - } - - super.startHiding(nextTabId, hintAtTabSelection); - - // Reset mIsActiveLayout here instead of in doneHiding() so if a user hits the tab switcher - // button on the toolbar to re-open it while we're still in the process of hiding the tab - // switcher, we don't skip the logging. - mIsActiveLayout = false; - - if (mCurrentTabIdWhenOpened == nextTabId) { - RecordUserAction.record("MobileTabReturnedToCurrentTab"); - } - } - - @Override - public void doneHiding() { - // Log offset between newly-selected and previously-active tabs. A positive offset means the - // user switched to a tab earlier in the stack. A negative offset means the user switched to - // a tab later in the stack. 0 means they stayed on the same tab. We do not log anything if - // the user switched between stacks (normal to incognito or vice-versa). We also do not log - // anything if the tab switch was the result of a new tab being created (we do log for - // presses of the tab switcher button on the toolbar). - - // Note: we log this in doneHiding() instead of startHiding() because Layout#doneHiding() is - // where the new tab actually gets selected. If the user immediately reopens the tab - // switcher before the close animation finishes, the new tab doesn't actually get selected. - if (!mIsHidingBecauseOfNewTabCreation - && mModelIndexWhenOpened == mTabModelSelector.getCurrentModelIndex()) { - final int currentIndex = mTabModelSelector.getCurrentModel().index(); - final Tab newTab = mTabModelSelector.getTabById(mNextTabId); - final int newIndex = mTabModelSelector.getCurrentModel().indexOf(newTab); - assert newIndex != TabList.INVALID_TAB_INDEX; - RecordHistogram.recordSparseHistogram( - "Tabs.TabOffsetOfSwitch", currentIndex - newIndex); - } - mIsHidingBecauseOfNewTabCreation = false; - - super.doneHiding(); - RecordUserAction.record("MobileExitStackView"); - - mInnerMarginPercent = 0.0f; - mStackOffsetYPercent = 0.0f; - mTabModelSelector.commitAllTabClosures(); - } - - /** - * Extracts the tabs from a stack and append them into a list. - * @param stack The stack that contains the tabs. - * @param outList The output list where will be the tabs from the stack. - * @param index The current number of item in the outList. - * @return The updated index incremented by the number of tabs in the stack. - */ - private static int addAllTabs(Stack stack, StackTab[] outList, int index) { - StackTab[] stackTabs = stack.getTabs(); - if (stackTabs != null) { - for (int i = 0; i < stackTabs.length; ++i) { - outList[index++] = stackTabs[i]; - } - } - return index; - } - - /** - * Comparator that helps ordering StackTab's visibility sorting value in a decreasing order. - */ - private static class VisibilityComparator implements Comparator, Serializable { - @Override - public int compare(StackTab tab1, StackTab tab2) { - return (int) (tab2.getVisiblitySortingValue() - tab1.getVisiblitySortingValue()); - } - } - - /** - * Comparator that helps ordering StackTab's visibility sorting value in a decreasing order. - */ - private static class OrderComparator implements Comparator, Serializable { - @Override - public int compare(StackTab tab1, StackTab tab2) { - return tab1.getOrderSortingValue() - tab2.getOrderSortingValue(); - } - } - - /** - * Updates mSortedPriorityArray, which stores the list of StackTabs to render, sorted by - * rendering priority. - * - * @param comparator The comparator used to sort the StackTabs. - * @return True if at least one Stack has a tab, false if there are no tabs. - */ - private boolean updateSortedPriorityArray(Comparator comparator) { - int allTabsCount = 0; - for (int i = 0; i < mStacks.size(); i++) { - allTabsCount += mStacks.get(i).getCount(); - } - if (allTabsCount == 0) return false; - if (mSortedPriorityArray == null || mSortedPriorityArray.length != allTabsCount) { - mSortedPriorityArray = new StackTab[allTabsCount]; - } - int sortedOffset = 0; - for (int i = 0; i < mStacks.size(); i++) { - sortedOffset = addAllTabs(mStacks.get(i), mSortedPriorityArray, sortedOffset); - } - assert sortedOffset == mSortedPriorityArray.length; - Arrays.sort(mSortedPriorityArray, comparator); - return true; - } - - /** - * Updates the priority list of the {@link LayoutTab} and sends it the systems having processing - * to do on a per {@link LayoutTab} basis. Priority meaning may change based on the current - * comparator stored in {@link #mSortingComparator}. - * - * Do not use {@link #mSortedPriorityArray} out side this context. It is only a member to avoid - * doing an allocation every frames. - */ - private void updateTabPriority() { - if (!updateSortedPriorityArray(mSortingComparator)) return; - updateTabsVisibility(mSortedPriorityArray); - updateDelayedLayoutTabInit(mSortedPriorityArray); - } - - /** - * Updates the list of visible tab Id that the tab content manager is suppose to serve. The list - * is ordered by priority. The first ones must be in the manager, then the remaining ones should - * have at least approximations if possible. - * - * @param sortedPriorityArray The array of all the {@link StackTab} sorted by priority. - */ - private void updateTabsVisibility(StackTab[] sortedPriorityArray) { - mVisibilityArray.clear(); - for (int i = 0; i < sortedPriorityArray.length; i++) { - mVisibilityArray.add(sortedPriorityArray[i].getId()); - } - updateCacheVisibleIds(mVisibilityArray); - } - - /** - * Initializes the {@link LayoutTab} a few at a time. This function is to be called once a - * frame. - * The logic of that function is not as trivial as it should be because the input array we want - * to initialize the tab from keeps getting reordered from calls to call. This is needed to - * get the highest priority tab initialized first. - * - * @param sortedPriorityArray The array of all the {@link StackTab} sorted by priority. - */ - private void updateDelayedLayoutTabInit(StackTab[] sortedPriorityArray) { - if (!mDelayedLayoutTabInitRequired) return; - - int initialized = 0; - final int count = sortedPriorityArray.length; - for (int i = 0; i < count; i++) { - if (initialized >= LAYOUTTAB_ASYNCHRONOUS_INITIALIZATION_BATCH_SIZE) return; - - LayoutTab layoutTab = sortedPriorityArray[i].getLayoutTab(); - // The actual initialization is done by the parent class. - if (super.initLayoutTabFromHost(layoutTab)) { - initialized++; - } - } - if (initialized == 0) mDelayedLayoutTabInitRequired = false; - } - - @Override - protected boolean initLayoutTabFromHost(LayoutTab layoutTab) { - if (layoutTab.isInitFromHostNeeded()) mDelayedLayoutTabInitRequired = true; - return false; - } - - /** - * Called by the stacks whenever they start an animation. - */ - public void onStackAnimationStarted() { - mStackAnimationCount++; - } - - /** - * Called by the stacks whenever they finish their animations. - */ - public void onStackAnimationFinished() { - mStackAnimationCount--; - } - - @Override - protected EventFilter getEventFilter() { - return mGestureEventFilter; - } - - @Override - protected SceneLayer getSceneLayer() { - return mSceneLayer; - } - - private void ensureSceneLayerCreated() { - if (mSceneLayer != null) return; - mSceneLayer = new TabListSceneLayer(); - } - - @Override - protected void updateSceneLayer(RectF viewport, RectF contentViewport, - TabContentManager tabContentManager, ResourceManager resourceManager, - BrowserControlsStateProvider browserControls) { - ensureSceneLayerCreated(); - super.updateSceneLayer( - viewport, contentViewport, tabContentManager, resourceManager, browserControls); - assert mSceneLayer != null; - - mSceneLayer.pushLayers(getContext(), viewport, contentViewport, this, tabContentManager, - resourceManager, browserControls, SceneLayer.INVALID_RESOURCE_ID, 0, 0); - } - - @Override - public int getLayoutType() { - return LayoutType.TAB_SWITCHER; - } - - /** - * Creates an {@link CompositorAnimator} and adds it to the animation. - * Automatically sets the start value at the beginning of the animation. - */ - protected void addToAnimation(FloatProperty property, float start, float end, - long duration, long startTime) { - CompositorAnimator compositorAnimator = CompositorAnimator.ofFloatProperty( - getAnimationHandler(), this, property, start, end, duration); - compositorAnimator.setStartDelay(startTime); - compositorAnimator.start(); - - for (int i = mLayoutAnimations.size() - 1; i >= 0; i--) { - if (mLayoutAnimations.get(i).second == property - && !mLayoutAnimations.get(i).first.isRunning()) { - mLayoutAnimations.set(i, - new Pair(compositorAnimator, property)); - - requestUpdate(); - return; - } - } - - mLayoutAnimations.add( - new Pair(compositorAnimator, property)); - - requestUpdate(); - } - - @Override - protected void forceAnimationToFinish() { - super.forceAnimationToFinish(); - - for (int i = 0; i < mLayoutAnimations.size(); i++) { - mLayoutAnimations.get(i).first.end(); - } - mLayoutAnimations.clear(); - } - - /** - * Cancels any animation for the given object and property. - * @param object The object being animated. - * @param prop The property to search for. - */ - protected void cancelAnimation(FloatProperty property) { - for (int i = mLayoutAnimations.size() - 1; i >= 0; i--) { - if (mLayoutAnimations.get(i).second == property) { - mLayoutAnimations.get(i).first.cancel(); - } - } - } - - public boolean isLayoutAnimating() { - for (int i = 0; i < mLayoutAnimations.size(); i++) { - if (mLayoutAnimations.get(i).first.isRunning()) return true; - } - return false; - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java deleted file mode 100644 index 59de15da723f..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/OverlappingStack.java +++ /dev/null @@ -1,671 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone.stack; - -import android.content.Context; -import android.content.res.Resources; - -import org.chromium.base.MathUtils; -import org.chromium.chrome.R; -import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; -import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; -import org.chromium.chrome.browser.compositor.layouts.phone.StackLayoutBase; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackAnimation.OverviewAnimationType; -import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities; -import org.chromium.ui.base.LocalizationUtils; - -/** - * The overlapping tab stack we use when the HorizontalTabSwitcherAndroid flag is not enabled. - */ -public class OverlappingStack extends Stack { - private static final float SCALE_AMOUNT = 0.90f; - - /** - * The percentage of the screen that defines the spacing between tabs by default (no pinch). - */ - private static final float SPACING_SCREEN = 0.26f; - - /** - * Percentage of the screen to wrap the scroll space. - */ - private static final float SCROLL_WARP_PCTG = 0.4f; - - /** - * How much the stack should adjust the y position of each LayoutTab in portrait mode (as a - * fraction of the amount space that would be above and below the tab if it were centered). - */ - private static final float STACK_PORTRAIT_Y_OFFSET_PROPORTION = -0.8f; - - /** - * How much the stack should adjust the x position of each LayoutTab in landscape mode (as a - * fraction of the amount space that would be to the left and right of the tab if it were - * centered). - */ - private static final float STACK_LANDSCAPE_START_OFFSET_PROPORTION = -0.7f; - - /** - * How much the stack should adjust the x position of each LayoutTab in portrait mode (as a - * fraction of the amount space that would be above and below the tab if it were centered). - */ - private static final float STACK_LANDSCAPE_Y_OFFSET_PROPORTION = -0.5f; - - private float mWarpSize; - - // During pinch, the finger the closest to the bottom of the stack changes the scrolling - // and the other finger locally stretches the spacing between the tabs. - private int mPinch0TabIndex = -1; - private int mPinch1TabIndex = -1; - private float mLastPinch0Offset; - private float mLastPinch1Offset; - - // Current progress of the 'even out' phase. This progress as the screen get scrolled. - private float mEvenOutProgress = 1.0f; - // Rate to even out all the tabs. - private float mEvenOutRate = 1.0f; // This will be updated from dimens.xlm - - private float mMinSpacing; // This will be updated from dimens.xml - - /** - * @param layout The parent layout. - */ - public OverlappingStack(Context context, StackLayoutBase layout) { - super(context, layout); - } - - @Override - public float getScaleAmount() { - return SCALE_AMOUNT; - } - - @Override - protected boolean evenOutTabs(float amount, boolean allowReverseDirection) { - if (mStackTabs == null || mOverviewAnimationType != OverviewAnimationType.NONE - || mEvenOutProgress >= 1.0f || amount == 0) { - return false; - } - boolean changed = false; - boolean reverseScrolling = false; - - // The evening out process last until mEvenOutRate reaches 1.0. Tabs blend linearly - // between the current position to a nice evenly scaled pattern. Because we do not store - // the starting position for each tab we need more complicated math to do the blend. - // The absoluteProgress is how much we need progress this step on the [0, 1] scale. - float absoluteProgress = Math.min(Math.abs(amount) * mEvenOutRate, 1.0f - mEvenOutProgress); - // The relativeProgress is how much we need to blend the target to the current to get there. - float relativeProgress = absoluteProgress / (1.0f - mEvenOutProgress); - - float screenMax = getScrollDimensionSize(); - for (int i = 0; i < mStackTabs.length; ++i) { - float source = mStackTabs[i].getScrollOffset(); - float target = screenToScroll(i * mSpacing); - float sourceScreen = Math.min(screenMax, scrollToScreen(source + mScrollTarget)); - float targetScreen = Math.min(screenMax, scrollToScreen(target + mScrollTarget)); - // If the target and the current position matches on the screen then we snap to the - // target. - if (sourceScreen == targetScreen) { - mStackTabs[i].setScrollOffset(target); - continue; - } - float step = source + (target - source) * relativeProgress; - float stepScreen = Math.min(screenMax, scrollToScreen(step + mScrollTarget)); - // If the step can be performed without noticing then we do it. - if (sourceScreen == stepScreen) { - mStackTabs[i].setScrollOffset(step); - continue; - } - // If the scrolling goes in the same direction as the step then the motion is applied. - if ((targetScreen - sourceScreen) * amount > 0 || allowReverseDirection) { - mStackTabs[i].setScrollOffset(step); - changed = true; - } else { - reverseScrolling = true; - } - } - // Only account for progress if the scrolling was in the right direction. It assumes here - // That if any of the tabs was going in the wrong direction then the progress is not - // recorded at all. This is very conservative to avoid poping in the scrolling. It works - // for now but might need to be revisited if we see artifacts. - if (!reverseScrolling) { - mEvenOutProgress += absoluteProgress; - } - return changed; - } - - @Override - public void onLongPress(long time, float x, float y) { - if (mOverviewAnimationType == OverviewAnimationType.NONE) { - int longPressSelected = getTabIndexAtPositon(x, y); - if (longPressSelected >= 0) { - startAnimation(time, OverviewAnimationType.VIEW_MORE, longPressSelected, false); - mEvenOutProgress = 0.0f; - } - } - } - - @Override - public void onPinch(long time, float x0, float y0, float x1, float y1, boolean firstEvent) { - if ((mOverviewAnimationType != OverviewAnimationType.START_PINCH - && mOverviewAnimationType != OverviewAnimationType.NONE) - || mStackTabs == null) { - return; - } - if (mPinch0TabIndex < 0) startAnimation(time, OverviewAnimationType.START_PINCH); - - // Reordering the fingers so pinch0 is always the closest to the top of the stack. - // This allows simpler math down the line where we assume that - // pinch0TabIndex <= pinch0TabIndex - // It also means that crossing the finger will separate the tabs again. - boolean inverse = (mCurrentMode == Orientation.PORTRAIT) - ? y0 > y1 - : LocalizationUtils.isLayoutRtl() ? (x0 <= x1) : (x0 > x1); - float pinch0X = inverse ? x1 : x0; - float pinch0Y = inverse ? y1 : y0; - float pinch1X = inverse ? x0 : x1; - float pinch1Y = inverse ? y0 : y1; - float pinch0Offset = (mCurrentMode == Orientation.PORTRAIT) - ? pinch0Y - : LocalizationUtils.isLayoutRtl() ? -pinch0X : pinch0X; - float pinch1Offset = (mCurrentMode == Orientation.PORTRAIT) - ? pinch1Y - : LocalizationUtils.isLayoutRtl() ? -pinch1X : pinch1X; - - if (firstEvent) { - // Resets pinch and scrolling state. - mPinch0TabIndex = -1; - mPinch1TabIndex = -1; - mScrollingTab = null; - commitDiscard(time, false); - } - int pinch0TabIndex = mPinch0TabIndex; - int pinch1TabIndex = mPinch1TabIndex; - if (mPinch0TabIndex < 0) { - pinch0TabIndex = getTabIndexAtPositon(pinch0X, pinch0Y); - pinch1TabIndex = getTabIndexAtPositon(pinch1X, pinch1Y); - // If any of them is invalid we invalidate both. - if (pinch0TabIndex < 0 || pinch1TabIndex < 0) { - pinch0TabIndex = -1; - pinch1TabIndex = -1; - } - } - - if (pinch0TabIndex >= 0 && mPinch0TabIndex == pinch0TabIndex - && mPinch1TabIndex == pinch1TabIndex) { - final float minScrollTarget = getMinScroll(false); - final float maxScrollTarget = getMaxScroll(false); - final float oldScrollTarget = - MathUtils.clamp(mScrollTarget, minScrollTarget, maxScrollTarget); - // pinch0TabIndex > pinch1TabIndex is unexpected but we do not want to exit - // ungracefully so process it as if the tabs were the same. - if (pinch0TabIndex >= pinch1TabIndex) { - // If one tab is pinched then we only scroll. - float screenDelta0 = pinch0Offset - mLastPinch0Offset; - if (pinch0TabIndex == 0) { - // Linear scroll on the top tab for the overscroll to kick-in linearly. - setScrollTarget(oldScrollTarget + screenDelta0, false); - } else { - float tab0ScrollSpace = - mStackTabs[pinch0TabIndex].getScrollOffset() + oldScrollTarget; - float tab0Screen = scrollToScreen(tab0ScrollSpace); - float tab0ScrollFinal = screenToScroll(tab0Screen + screenDelta0); - setScrollTarget( - tab0ScrollFinal - mStackTabs[pinch0TabIndex].getScrollOffset(), false); - } - // This is the common case of the pinch, 2 fingers on 2 different tabs. - } else { - // Find the screen space position before and after the scroll so the tab 0 matches - // the finger 0 motion. - float screenDelta0 = pinch0Offset - mLastPinch0Offset; - float tab0ScreenBefore = approxScreen(mStackTabs[pinch0TabIndex], oldScrollTarget); - float tab0ScreenAfter = tab0ScreenBefore + screenDelta0; - - // Find the screen space position before and after the scroll so the tab 1 matches - // the finger 1 motion. - float screenDelta1 = pinch1Offset - mLastPinch1Offset; - float tab1ScreenBefore = approxScreen(mStackTabs[pinch1TabIndex], oldScrollTarget); - float tab1ScreenAfter = tab1ScreenBefore + screenDelta1; - - // Heuristic: the scroll is defined by half the change of the first pinched tab. - // The rational is that it looks nice this way :)... Scrolling creates a sliding - // effect. When a finger does not move then it is expected that none of the tabs - // past that steady finger should move. This does the job. - float globalScrollBefore = screenToScroll(tab0ScreenBefore); - float globalScrollAfter = screenToScroll((tab0ScreenAfter + tab0ScreenBefore) / 2); - setScrollTarget(oldScrollTarget + globalScrollAfter - globalScrollBefore, true); - - // Evens out the tabs in between - float minScreen = tab0ScreenAfter; - float maxScreen = tab0ScreenAfter; - for (int i = pinch0TabIndex; i <= pinch1TabIndex; i++) { - float screenBefore = approxScreen(mStackTabs[i], oldScrollTarget); - float t = (tab1ScreenBefore == tab0ScreenBefore) - ? 1 - : ((screenBefore - tab0ScreenBefore) - / (tab1ScreenBefore - tab0ScreenBefore)); - float screenAfter = (1 - t) * tab0ScreenAfter + t * tab1ScreenAfter; - screenAfter = Math.max(minScreen, screenAfter); - screenAfter = Math.min(maxScreen, screenAfter); - minScreen = screenAfter + StackTab.sStackedTabVisibleSize; - maxScreen = screenAfter + mStackTabs[i].getSizeInScrollDirection(mCurrentMode); - float newScrollOffset = screenToScroll(screenAfter) - mScrollTarget; - mStackTabs[i].setScrollOffset(newScrollOffset); - } - - // Push a bit the tabs bellow pinch1. - float delta1 = tab1ScreenAfter - tab1ScreenBefore; - for (int i = pinch1TabIndex + 1; i < mStackTabs.length; i++) { - delta1 /= 2; - float screenAfter = approxScreen(mStackTabs[i], oldScrollTarget) + delta1; - screenAfter = Math.max(minScreen, screenAfter); - screenAfter = Math.min(maxScreen, screenAfter); - minScreen = screenAfter + StackTab.sStackedTabVisibleSize; - maxScreen = screenAfter + mStackTabs[i].getSizeInScrollDirection(mCurrentMode); - mStackTabs[i].setScrollOffset(screenToScroll(screenAfter) - mScrollTarget); - } - - // Pull a bit the tabs above pinch0. - minScreen = tab0ScreenAfter; - maxScreen = tab0ScreenAfter; - float posScreen = tab0ScreenAfter; - float delta0 = tab0ScreenAfter - tab0ScreenBefore; - for (int i = pinch0TabIndex - 1; i > 0; i--) { - delta0 /= 2; - minScreen = posScreen - mStackTabs[i].getSizeInScrollDirection(mCurrentMode); - maxScreen = posScreen - StackTab.sStackedTabVisibleSize; - float screenAfter = approxScreen(mStackTabs[i], oldScrollTarget) + delta0; - screenAfter = Math.max(minScreen, screenAfter); - screenAfter = Math.min(maxScreen, screenAfter); - mStackTabs[i].setScrollOffset(screenToScroll(screenAfter) - mScrollTarget); - } - } - } - mPinch0TabIndex = pinch0TabIndex; - mPinch1TabIndex = pinch1TabIndex; - mLastPinch0Offset = pinch0Offset; - mLastPinch1Offset = pinch1Offset; - mEvenOutProgress = 0.0f; - mLayout.requestUpdate(); - } - - @Override - public void onUpOrCancel(long time) { - // Make sure the bottom tab always goes back to the top of the screen. - if (mPinch0TabIndex >= 0) { - startAnimation(time, OverviewAnimationType.REACH_TOP); - mLayout.requestUpdate(); - } - - super.onUpOrCancel(time); - } - - @Override - protected void springBack(long time) { - if (mScroller.isFinished()) { - int minScroll = (int) getMinScroll(false); - int maxScroll = (int) getMaxScroll(false); - if (mScrollTarget < minScroll || mScrollTarget > maxScroll) { - mScroller.springBack(0, (int) mScrollTarget, 0, 0, minScroll, maxScroll, time); - setScrollTarget(MathUtils.clamp(mScrollTarget, minScroll, maxScroll), false); - mLayout.requestUpdate(); - } - } - } - - /** - * @param context The current Android's context. - */ - @Override - public void contextChanged(Context context) { - super.contextChanged(context); - - Resources res = context.getResources(); - final float pxToDp = 1.0f / res.getDisplayMetrics().density; - - mEvenOutRate = 1.0f / (res.getDimension(R.dimen.even_out_scrolling) * pxToDp); - mMinSpacing = res.getDimensionPixelOffset(R.dimen.min_spacing) * pxToDp; - } - - @Override - protected boolean shouldStackTabsAtTop() { - return true; - } - - @Override - protected boolean shouldStackTabsAtBottom() { - return true; - } - - @Override - protected float getStackPortraitYOffsetProportion() { - return STACK_PORTRAIT_Y_OFFSET_PROPORTION; - } - - @Override - protected float getStackLandscapeStartOffsetProportion() { - return STACK_LANDSCAPE_START_OFFSET_PROPORTION; - } - - @Override - protected float getStackLandscapeYOffsetProportion() { - return STACK_LANDSCAPE_Y_OFFSET_PROPORTION; - } - - @Override - protected float getSpacingScreen() { - return SPACING_SCREEN; - } - - @Override - protected boolean shouldCloseGapsBetweenTabs() { - return true; - } - - @Override - protected void computeTabClippingVisibilityHelper() { - // alpha override, clipping and culling. - final boolean portrait = mCurrentMode == Orientation.PORTRAIT; - - // Iterate through each tab starting at the top of the stack and working - // backwards. Set the clip on each tab such that it does not extend past - // the beginning of the tab above it. clipOffset is used to keep track - // of where the previous tab started. - float clipOffset; - if (portrait) { - // portrait LTR & RTL - clipOffset = mLayout.getHeight() + StackTab.sStackedTabVisibleSize; - } else if (!LocalizationUtils.isLayoutRtl()) { - // landscape LTR - clipOffset = mLayout.getWidth() + StackTab.sStackedTabVisibleSize; - } else { - // landscape RTL - clipOffset = -StackTab.sStackedTabVisibleSize; - } - - for (int i = mStackTabs.length - 1; i >= 0; i--) { - LayoutTab layoutTab = mStackTabs[i].getLayoutTab(); - layoutTab.setVisible(true); - - // Don't bother with clipping tabs that are dying, rotating, with an X offset, or - // non-opaque. - if (mStackTabs[i].isDying() || mStackTabs[i].getXInStackOffset() != 0.0f - || layoutTab.getAlpha() < 1.0f) { - layoutTab.setClipOffset(0.0f, 0.0f); - layoutTab.setClipSize(Float.MAX_VALUE, Float.MAX_VALUE); - continue; - } - - // The beginning, size, and clipped size of the current tab. - float tabOffset; - float tabSize; - float tabClippedSize; - float borderAdjustmentSize; - float insetBorderPadding; - if (portrait) { - // portrait LTR & RTL - tabOffset = layoutTab.getY(); - tabSize = layoutTab.getScaledContentHeight(); - tabClippedSize = Math.min(tabSize, clipOffset - tabOffset); - borderAdjustmentSize = mBorderTransparentTop; - insetBorderPadding = mBorderTopPadding; - } else if (!LocalizationUtils.isLayoutRtl()) { - // landscape LTR - tabOffset = layoutTab.getX(); - tabSize = layoutTab.getScaledContentWidth(); - tabClippedSize = Math.min(tabSize, clipOffset - tabOffset); - borderAdjustmentSize = mBorderTransparentSide; - insetBorderPadding = 0; - } else { - // landscape RTL - tabOffset = layoutTab.getX() + layoutTab.getScaledContentWidth(); - tabSize = layoutTab.getScaledContentWidth(); - tabClippedSize = Math.min(tabSize, tabOffset - clipOffset); - borderAdjustmentSize = -mBorderTransparentSide; - insetBorderPadding = 0; - } - - float absBorderAdjustmentSize = Math.abs(borderAdjustmentSize); - - if (tabClippedSize <= absBorderAdjustmentSize) { - // If the tab is completed covered, don't bother drawing it at all. - layoutTab.setVisible(false); - layoutTab.setDrawDecoration(true); - mLayout.releaseResourcesForTab(layoutTab); - } else { - // Fade the tab as it gets too close to the next one. This helps - // prevent overlapping shadows from becoming too dark. - float fade = MathUtils.clamp(((tabClippedSize - absBorderAdjustmentSize) - / StackTab.sStackedTabVisibleSize), - 0, 1); - layoutTab.setDecorationAlpha(fade); - - // When tabs tilt forward, it will expose more of the tab - // underneath. To compensate, make the clipping size larger. - // Note, this calculation is only an estimate that seems to - // work. - float clipScale = 1.0f; - float scaledTabClippedSize = Math.min(tabClippedSize * clipScale, tabSize); - // Set the clip - layoutTab.setClipOffset((!portrait && LocalizationUtils.isLayoutRtl()) - ? (tabSize - scaledTabClippedSize) - : 0, - 0); - layoutTab.setClipSize(portrait ? Float.MAX_VALUE : scaledTabClippedSize, - portrait ? scaledTabClippedSize : Float.MAX_VALUE); - } - - // Clip the next tab where this tab begins. - if (i > 0) { - LayoutTab nextLayoutTab = mStackTabs[i - 1].getLayoutTab(); - if (nextLayoutTab.getScale() <= layoutTab.getScale()) { - clipOffset = tabOffset; - } else { - clipOffset = tabOffset + tabClippedSize * layoutTab.getScale(); - } - - // Extend the border just a little bit. Otherwise, the - // rounded borders will intersect and make it look like the - // content is actually smaller. - clipOffset += borderAdjustmentSize; - - if (layoutTab.getBorderAlpha() < 1.f && layoutTab.getToolbarAlpha() < 1.f) { - clipOffset += insetBorderPadding; - } - } - } - } - - @Override - protected int computeReferenceIndex() { - int centerIndex = - getTabIndexAtPositon(mLayout.getWidth() / 2.0f, mLayout.getHeight() / 2.0f); - // Alter the center to take into account the scrolling direction. - if (mCurrentScrollDirection > 0) centerIndex++; - if (mCurrentScrollDirection < 0) centerIndex--; - return MathUtils.clamp(centerIndex, 0, mStackTabs.length - 1); - } - - @Override - protected float getMinScroll(boolean allowUnderScroll) { - float maxOffset = 0; - if (mStackTabs != null) { - // The tabs are not always ordered so we need to browse them all. - for (int i = 0; i < mStackTabs.length; i++) { - if (!mStackTabs[i].isDying() && mStackTabs[i].getLayoutTab().isVisible()) { - maxOffset = Math.max(mStackTabs[i].getScrollOffset(), maxOffset); - } - } - } - return (allowUnderScroll ? -mMaxUnderScroll : 0) - maxOffset; - } - - @Override - protected int computeSpacing(int layoutTabCount) { - int spacing = 0; - if (layoutTabCount > 1) { - final float dimension = getScrollDimensionSize(); - int minSpacing = (int) Math.max(dimension * SPACING_SCREEN, mMinSpacing); - if (mStackTabs != null) { - for (int i = 0; i < mStackTabs.length; i++) { - assert mStackTabs[i] != null; - if (!mStackTabs[i].isDying()) { - minSpacing = (int) Math.min( - minSpacing, mStackTabs[i].getSizeInScrollDirection(mCurrentMode)); - } - } - } - spacing = (int) ((dimension - 20) / (layoutTabCount * .8f)); - spacing = Math.max(spacing, minSpacing); - } - return spacing; - } - - @Override - protected boolean allowOverscroll() { - return super.allowOverscroll() && mPinch0TabIndex < 0; - } - - @Override - protected void resetAllScrollOffset() { - if (mTabList == null) return; - // Reset the scroll position to put the important {@link StackTab} into focus. - // This does not scroll the {@link StackTab}s there but rather moves everything - // there immediately. - // The selected tab is supposed to show at the center of the screen. - float maxTabsPerPage = getScrollDimensionSize() / mSpacing; - float centerOffsetIndex = maxTabsPerPage / 2.0f - 0.5f; - final int count = mTabList.getCount(); - final int index = mTabList.index(); - if (index < centerOffsetIndex || count <= maxTabsPerPage) { - mScrollOffset = 0; - } else if (index == count - 1 && Math.ceil(maxTabsPerPage) < count) { - mScrollOffset = (maxTabsPerPage - count - 1) * mSpacing; - } else if ((count - index - 1) < centerOffsetIndex) { - mScrollOffset = (maxTabsPerPage - count) * mSpacing; - } else { - mScrollOffset = (centerOffsetIndex - index) * mSpacing; - } - // Reset the scroll offset of the tabs too. - if (mStackTabs != null) { - for (int i = 0; i < mStackTabs.length; i++) { - mStackTabs[i].setScrollOffset(screenToScroll(i * mSpacing)); - } - } - setScrollTarget(mScrollOffset, false); - } - - /** - * Unwarps x so it matches the above warp function. - * @see #scrollToScreen(float) - * - * [-oo, 0] -> -warpSize - * [0, warpSize] -> 2 * warpSize * sqrt(x / warpSize). - * [warpSize, +oo] -> x + warpSize - * @param x The screen space offset. - * @param warpSize The size in scroll space of the slow down effect. - * @return The offset in scroll space corresponding to the offset on screen. - */ - private float screenToScroll(float x, float warpSize) { - if (x <= 0) return 0; - if (x >= warpSize) return x + warpSize; - return (float) Math.sqrt(x * warpSize) * 2; - } - - /** - * Public version of screenToScroll(float, float) that uses the current warp size. - * @param scrollSpace The offset in screen space. - * @return The offset in scroll space corresponding to the offset on screen. - */ - @Override - public float screenToScroll(float screenSpace) { - return screenToScroll(screenSpace, mWarpSize); - } - - /** - * The scroll space does not map linearly to the screen so it creates a nice slow down - * effect at the top of the screen while scrolling. - * Warps x so it matches y(x) = x - warpSize on the positive side and 0 on the negative side - * with a smooth transition between [0, 2 * warpSize]. - * @see #screenToScroll(float) - * - * [-oo, 0] -> 0 - * [0, 2 * warpSize] -> warpSize * ((x-warpSize) / 2 * warpSize + 0.5) ^ 2. - * [2 * warpSize, +oo] -> x - * @param x The offset in scroll space. - * @param warpSize The size in scroll space of the slow down effect. - * @return The offset on screen corresponding to the scroll space offset. - */ - private float scrollToScreen(float x, float warpSize) { - if (x <= 0) return 0; - if (x >= 2 * warpSize) return x - warpSize; - x = (x - warpSize) / (2.0f * warpSize) + 0.5f; - return x * x * warpSize; - } - - /** - * Public version of scrollToScreen(float, float) that uses the current warp size. - * Maps from scroll coordinates to screen coordinates. - * @param scrollSpace The offset in scroll space. - * @return The offset on screen corresponding to the scroll space offset. - */ - @Override - public float scrollToScreen(float scrollSpace) { - return scrollToScreen(scrollSpace, mWarpSize); - } - - @Override - public float getMaxTabHeight() { - // TODO(crbug.com/1095698): Rework when the stack enter animation is created so that we can - // remove this feature specific fix. - // When conditional tab strip is enabled, the bottom browser control height should be 0 - // eventually when overview mode is visible. Hence, we pre-acknowledge the fact and assume - // the bottom control height to be 0 here so that the animation is correctly set up. - if (TabUiFeatureUtilities.isConditionalTabStripEnabled()) { - return mLayout.getHeight() - mLayout.getTopContentOffsetDp(); - } - return mLayout.getHeightMinusContentOffsetsDp(); - } - - @Override - protected void updateCurrentMode(@Orientation int orientation) { - setWarpState(true, false); - super.updateCurrentMode(orientation); - } - - @Override - protected void resetInputActionIndices() { - super.resetInputActionIndices(); - - mPinch0TabIndex = -1; - mPinch1TabIndex = -1; - } - - /** - * Whether or not the tab positions warp from linear to nonlinear as the tabs approach the edge - * of the screen. This allows us to move the tabs to linear space to track finger movements, - * but also move them back to non-linear space without any visible change to the user. - * @param canWarp Whether or not the tabs are allowed to warp. - * @param adjustCurrentTabs Whether or not to change the tab positions so there's no visible - * difference after the change. - */ - private void setWarpState(boolean canWarp, boolean adjustCurrentTabs) { - float warp = canWarp ? getScrollDimensionSize() * SCROLL_WARP_PCTG : 0.f; - - if (mStackTabs != null && adjustCurrentTabs && Float.compare(warp, mWarpSize) != 0) { - float scrollOffset = - MathUtils.clamp(mScrollOffset, getMinScroll(false), getMaxScroll(false)); - for (int i = 0; i < mStackTabs.length; i++) { - StackTab tab = mStackTabs[i]; - float tabScrollOffset = tab.getScrollOffset(); - float tabScrollSpace = tabScrollOffset + scrollOffset; - float tabScreen = scrollToScreen(tabScrollSpace, mWarpSize); - float tabScrollSpaceFinal = screenToScroll(tabScreen, warp); - float scrollDelta = tabScrollSpaceFinal - tabScrollSpace; - tab.setScrollOffset(tabScrollOffset + scrollDelta); - } - } - - mWarpSize = warp; - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java deleted file mode 100644 index 8aa9c4862e59..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java +++ /dev/null @@ -1,2064 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone.stack; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.RectF; - -import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.MathUtils; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.chrome.R; -import org.chromium.chrome.browser.compositor.layouts.Layout; -import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; -import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; -import org.chromium.chrome.browser.compositor.layouts.phone.StackLayoutBase; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackAnimation.OverviewAnimationType; -import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler; -import org.chromium.chrome.browser.layouts.animation.FloatProperty; -import org.chromium.chrome.browser.tab.Tab; -import org.chromium.chrome.browser.tabmodel.TabList; -import org.chromium.chrome.browser.tabmodel.TabModelUtils; -import org.chromium.ui.base.LocalizationUtils; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Handles all the drawing and events of a stack of stackTabs. - * - * @VisibleForTesting - */ -public abstract class Stack { - public static final int MAX_NUMBER_OF_STACKED_TABS_TOP = 3; - public static final int MAX_NUMBER_OF_STACKED_TABS_BOTTOM = 3; - - private static final float STACK_PORTRAIT_Y_OFFSET_PROPORTION = -0.8f; - private static final float STACK_LANDSCAPE_START_OFFSET_PROPORTION = -0.7f; - private static final float STACK_LANDSCAPE_Y_OFFSET_PROPORTION = -0.5f; - - @IntDef({DragLock.NONE, DragLock.SCROLL, DragLock.DISCARD}) - @Retention(RetentionPolicy.SOURCE) - public @interface DragLock { - int NONE = 0; - int SCROLL = 1; - int DISCARD = 2; - } - - /** - * The percentage of the screen to cover for the discarded tab to be fully transparent. - */ - public static final float DISCARD_RANGE_SCREEN = 0.7f; - - /** - * The percentage the tab need to be dragged to actually discard the card. - */ - private static final float DISCARD_COMMIT_THRESHOLD = 0.4f; - - /** - * The percentage of the side of the tab that is inactive to swipe to discard. As this is - * a distance computed from both edges, meaningful value ranges in [0 ... 0.5]. - */ - private static final float DISCARD_SAFE_SELECTION_PCTG = 0.1f; - - /** - * The minimum scale the tab can reach when being discarded by a click. - */ - private static final float DISCARD_END_SCALE_CLICK = 0.7f; - - /** - * The minimum scale the tab can reach when being discarded by a swipe. - */ - private static final float DISCARD_END_SCALE_SWIPE = 0.5f; - - /** - * The delta time applied on the velocity from the fling. This is to compute the kick to - * help discarding a card. - */ - private static final float DISCARD_FLING_DT = 1.0f / 45.0f; - - /** - * The maximum contribution of the fling. This is in percentage of the range. - */ - private static final float DISCARD_FLING_MAX_CONTRIBUTION = 0.4f; - - /** - * How much to scale the max overscroll angle when tabs are tilting backwards. - */ - private static final float BACKWARDS_TILT_SCALE = 0.5f; - - /** - * When overscrolling towards the top or left of the screen, what portion of - * the overscroll should be devoted to sliding the tabs together. The rest - * of the overscroll is used for tilting. - */ - private static final float OVERSCROLL_TOP_SLIDE_PCTG = 0.25f; - - /** - * Scale max under/over scroll by this amount when flinging. - */ - private static final float MAX_OVER_FLING_SCALE = 0.5f; - - /** - * mMaxUnderScroll is determined by multing mMaxOverScroll with - * MAX_UNDER_SCROLL_SCALE - */ - private static final float MAX_UNDER_SCROLL_SCALE = 2.0f; - - /** - * Drags that are mostly horizontal (within 30 degrees) signal that - * a user is discarding a tab. - */ - private static final float DRAG_ANGLE_THRESHOLD = (float) Math.tan(Math.toRadians(30.0)); - - /** - * Reset the scroll mode after this number of milliseconds of inactivity or small motions. - */ - private static final long DRAG_TIME_THRESHOLD = 400; - - /** - * Minimum motion threshold to lock the scroll mode. - */ - private static final float DRAG_MOTION_THRESHOLD_DP = 1.25f; - - /** - * The number of attempt to get the full roll overscroll animation. - */ - private static final int OVERSCROLL_FULL_ROLL_TRIGGER = 5; - - /** - * Percentage of the screen to wrap the scroll space. - */ - private static final float SCROLL_WARP_PCTG = 0.4f; - - /** - * Percentage of the screen a swipe gesture must traverse before it is allowed to be - * canceled. - */ - private static final float SWIPE_LANDSCAPE_THRESHOLD = 0.19f; - - /** - * How far to place the tab to the left of the user's finger when swiping in dp. This keeps - * the tab under the user's finger. - */ - private static final float LANDSCAPE_SWIPE_DRAG_TAB_OFFSET_DP = 40.f; - - // TODO(dtrainor): Investigate removing this. - private static final float BORDER_THICKNESS_DP = 4.f; - - // External References - protected TabList mTabList; - - // True when the stack is still visible for animation but it is going to be empty. - private boolean mIsDying; - - // Screen State Variables - protected int mSpacing; - protected StackTab[] mStackTabs; // mStackTabs can be null if there are no tabs - - // Overscroll - protected StackScroller mScroller; - private float mOverScrollOffset; - private int mOverScrollDerivative; - private int mOverScrollCounter; - private float mMaxOverScroll; // This will be updated from dimens.xml - protected float mMaxUnderScroll; - protected float mMaxOverScrollAngle; // This will be updated from values.xml - private float mMaxOverScrollSlide; - - // Drag Lock - private @DragLock int mDragLock = DragLock.NONE; - private long mLastScrollUpdate; - private float mMinScrollMotion; - - // Scrolling Variables - protected float mScrollTarget; - protected float mScrollOffset; - private float mScrollOffsetForDyingTabs; - protected float mCurrentScrollDirection; - protected StackTab mScrollingTab; - - // Swipe Variables - private float mSwipeUnboundScrollOffset; - private float mSwipeBoundedScrollOffset; - private boolean mSwipeIsCancelable; - private boolean mSwipeCanScroll; - protected boolean mInSwipe; - - // Discard - protected StackTab mDiscardingTab; - - // We can't initialize mDiscardDirection here using LocalizationUtils.isRtl() because it - // will involve a jni call. Instead, mDiscardDirection will be initialized in Show(). - private float mDiscardDirection = Float.NaN; - - private int mReferenceOrderIndex = -1; - - // Orientation Variables - protected @Orientation int mCurrentMode = Orientation.PORTRAIT; - - // Animation Variables - protected @OverviewAnimationType int mOverviewAnimationType = OverviewAnimationType.NONE; - private StackAnimation mAnimationFactory; - private StackViewAnimation mViewAnimationFactory; - - // Running set of animations applied to tabs. - private StackAnimation.StackAnimatorSet mStackAnimatorSet; - private Animator mViewAnimations; - - // The parent Layout - protected final StackLayoutBase mLayout; - - // Border values - protected float mBorderTransparentTop; - protected float mBorderTransparentSide; - // TODO(dtrainor): Expose 9-patch padding from resource manager. - protected float mBorderTopPadding; - private float mBorderLeftPadding; - - // The slop amount in dp to detect a touch on the tab. Cached values from values/dimens.xml. - private float mCompositorButtonSlop; // compositor_button_slop - - private boolean mIsStackForCurrentTabList; - - private final AnimatorListenerAdapter mViewAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(Animator animation) { - mLayout.requestUpdate(); - } - - @Override - public void onAnimationEnd(Animator animation) { - mLayout.requestUpdate(); - } - }; - - /** - * @param layout The parent layout. - */ - public Stack(Context context, StackLayoutBase layout) { - mLayout = layout; - contextChanged(context); - } - - /** - * @return Animation handler associated with this stack. - */ - public CompositorAnimationHandler getAnimationHandler() { - return mLayout.getAnimationHandler(); - } - - /** - * @param tabList The list to attach to this stack. - */ - public void setTabList(TabList tabList) { - mTabList = tabList; - } - - /** - * @return The TabList associated with this stack. - */ - public TabList getTabList() { - return mTabList; - } - - /** - * @return The {@link StackTab}s currently being rendered by the tab stack. - * @VisibleForTesting - */ - public StackTab[] getTabs() { - return mStackTabs; - } - - /** - * @return The number of tabs in the tab stack. - * @VisibleForTesting - */ - public int getCount() { - return mStackTabs != null ? mStackTabs.length : 0; - } - - /** - * @return The number of visible tabs in the tab stack. - */ - public int getVisibleCount() { - int visibleCount = 0; - if (mStackTabs != null) { - for (int i = 0; i < mStackTabs.length; ++i) { - if (mStackTabs[i].getLayoutTab().isVisible()) visibleCount++; - } - } - return visibleCount; - } - - /** - * The scale the tabs should be currently shown at (may change based on how many are open). - */ - public abstract float getScaleAmount(); - - /* - * Main Interaction Methods for the rest of the application - * - * - * These methods are the main entry points for the model to tell the - * view that something has changed. The rest of the application can - * alert this class that something in the tab stack has changed or that - * the user has decided to enter the tab switcher. - * - */ - - /** - * Triggers the closing motions. - * - * @param time The current time of the app in ms. - * @param id The id of the tab that get closed. - */ - public void tabClosingEffect(long time, int id) { - if (mStackTabs == null) return; - - // |id| cannot be used to access the particular tab in the model. - // The tab is already gone from the model by this point. - - int newIndex = 0; - boolean needAnimation = false; - for (int i = 0; i < mStackTabs.length; ++i) { - if (mStackTabs[i].getId() == id) { - // Mark the {@link StackTab} as dying so that when the animation is - // finished we can clear it out of the stack. This supports - // multiple {@link StackTab} deletions. - needAnimation |= !mStackTabs[i].isDying(); - mStackTabs[i].setDying(true); - } else { - // Update the {@link StackTab} with a new index here. This makes sure the - // {@link LayoutTab} end up in the proper place. - mStackTabs[i].setNewIndex(newIndex++); - } - } - - if (needAnimation) { - mScrollOffsetForDyingTabs = mScrollOffset; - mSpacing = computeSpacing(newIndex); - - startAnimation(time, OverviewAnimationType.DISCARD); - } - - if (newIndex == 0) mIsDying = true; - } - - /** - * @return True if we should put the close button on the right side of the tab, or false if - * we should put it on the left. This method already accounts for RTL flipping. - */ - private boolean isCloseButtonOnRight() { - return mCurrentMode == Orientation.PORTRAIT ^ LocalizationUtils.isLayoutRtl(); - } - - /** - * Animates all the tabs closing at once. - * - * @param time The current time of the app in ms. - */ - public void tabsAllClosingEffect(long time) { - boolean needAnimation = false; - - if (mStackTabs != null) { - for (int i = 0; i < mStackTabs.length; ++i) { - needAnimation |= !mStackTabs[i].isDying(); - mStackTabs[i].setDying(true); - } - } else { - // This needs to be set to true to handle the case where both the normal and - // incognito tabs are being closed. - needAnimation = true; - } - - if (needAnimation) { - mScrollOffsetForDyingTabs = mScrollOffset; - mSpacing = computeSpacing(0); - - if (mStackTabs != null) { - for (int i = 0; i < mStackTabs.length; i++) { - StackTab tab = mStackTabs[i]; - tab.setDiscardOriginY(0.f); - tab.setDiscardOriginX(isCloseButtonOnRight() - ? tab.getLayoutTab().getOriginalContentWidth() - : 0.f); - tab.setDiscardFromClick(true); - } - } - startAnimation(time, OverviewAnimationType.DISCARD_ALL); - } - - mIsDying = true; - } - - /** - * Animates a new tab opening. - * - * @param time The current time of the app in ms. - * @param id The id of the new tab to animate. - */ - public void tabCreated(long time, int id) { - if (!createTabHelper(id)) return; - mIsDying = false; - - finishAnimation(time); - startAnimation(time, OverviewAnimationType.NEW_TAB_OPENED, - TabModelUtils.getTabIndexById(mTabList, id), TabList.INVALID_TAB_INDEX, false); - } - - /** - * Animates the closing of the stack. Focusing on the selected tab. - * - * @param time The current time of the app in ms. - * @param id The id of the tab to select. - */ - public void tabSelectingEffect(long time, int id) { - int index = TabModelUtils.getTabIndexById(mTabList, id); - startAnimation(time, OverviewAnimationType.TAB_FOCUSED, index, -1, false); - } - - /** - * Called set up the tab stack to the initial state when it is entered. - * - * @param time The current time of the app in ms. - * @param focused Whether or not the stack was focused when entering. - */ - public void stackEntered(long time, boolean focused) { - // Don't request new thumbnails until the animation is over. We should - // have cached the visible ones already. - boolean finishImmediately = !focused; - mSpacing = computeSpacing(mStackTabs != null ? mStackTabs.length : 0); - resetAllScrollOffset(); - startAnimation(time, OverviewAnimationType.ENTER_STACK, finishImmediately); - } - - /** - * @return Whether or not the TabList represented by this TabStackState should be displayed. - */ - public boolean isDisplayable() { - if (mTabList == null) return false; - - return !mTabList.isIncognito() || (!mIsDying && mTabList.getCount() > 0); - } - - private float getDefaultDiscardDirection() { - return (mCurrentMode == Orientation.LANDSCAPE && LocalizationUtils.isLayoutRtl()) ? -1.0f - : 1.0f; - } - - /** - * show is called to set up the initial variables, and must always be called before - * displaying the stack. - * @param isStackForCurrentTabList Whether this {@link Stack} is for the current tab list. - */ - public void show(boolean isStackForCurrentTabList) { - mIsStackForCurrentTabList = isStackForCurrentTabList; - - mDiscardDirection = getDefaultDiscardDirection(); - - // Reinitialize the roll over counter for each tabswitcher session. - mOverScrollCounter = 0; - - // TODO: Recreating the stack {@link StackTab} here might be overkill. Will these - // already exist in the cache? Check to make sure it makes sense. - createStackTabs(false); - } - - /* - * Animation Start and Finish Methods - * - * This method kicks off animations by using the - * TabSwitcherAnimationFactory to create an AnimatorSet. - */ - - /** - * Starts an animation on the stack. - * - * @param time The current time of the app in ms. - * @param type The type of the animation to start. - */ - protected void startAnimation(long time, @OverviewAnimationType int type) { - startAnimation(time, type, TabList.INVALID_TAB_INDEX, false); - } - - /** - * Starts an animation on the stack. - * - * @param time The current time of the app in ms. - * @param type The type of the animation to start. - * @param finishImmediately Whether the animation jumps straight to the end. - */ - private void startAnimation( - long time, @OverviewAnimationType int type, boolean finishImmediately) { - startAnimation(time, type, TabList.INVALID_TAB_INDEX, finishImmediately); - } - - /** - * Starts an animation on the stack. - * - * @param time The current time of the app in ms. - * @param type The type of the animation to start. - * @param sourceIndex The source index needed by some animation types. - * @param finishImmediately Whether the animation jumps straight to the end. - */ - protected void startAnimation(long time, @OverviewAnimationType int type, int sourceIndex, - boolean finishImmediately) { - startAnimation(time, type, mTabList.index(), sourceIndex, finishImmediately); - } - - private void startAnimation(long time, @OverviewAnimationType int type, int focusIndex, - int sourceIndex, boolean finishImmediately) { - if (!canUpdateAnimation(time, type, sourceIndex, finishImmediately)) { - // We need to finish animations started earlier before we start - // off a new one. - finishAnimation(time); - // Stop movement while the animation takes place. - stopScrollingMovement(time); - } - - if (mAnimationFactory != null && mViewAnimationFactory != null) { - mOverviewAnimationType = type; - - // First try to build a View animation. Then fallback to the compositor animation - // if one isn't created. - mViewAnimations = mViewAnimationFactory.createAnimatorForType( - type, mStackTabs, mLayout.getViewContainer(), mTabList, focusIndex); - - if (mViewAnimations != null) { - mViewAnimations.addListener(mViewAnimatorListener); - } else { - // Build the AnimatorSet using the TabSwitcherAnimationFactory. - // This will give us the appropriate AnimatorSet based on the current - // state of the tab switcher and the OverviewAnimationType specified. - mStackAnimatorSet = mAnimationFactory.createAnimatorSetForType(type, this, - mStackTabs, focusIndex, sourceIndex, mSpacing, getDiscardRange()); - } - - if (mStackAnimatorSet != null) mStackAnimatorSet.start(); - if (mViewAnimations != null) mViewAnimations.start(); - if (mStackAnimatorSet != null || mViewAnimations != null) { - mLayout.onStackAnimationStarted(); - } - - if ((mStackAnimatorSet == null && mViewAnimations == null) || finishImmediately) { - finishAnimation(time); - } - } - - mLayout.requestUpdate(); - } - - /** - * Performs the necessary actions to finish the current animation. - * - * @param time The current time of the app in ms. - */ - protected void finishAnimation(long time) { - if (mStackAnimatorSet != null) mStackAnimatorSet.end(); - if (mViewAnimations != null) mViewAnimations.end(); - if (mStackAnimatorSet != null || mViewAnimations != null) { - mLayout.onStackAnimationFinished(); - } - - switch (mOverviewAnimationType) { - case OverviewAnimationType.ENTER_STACK: - mLayout.uiDoneEnteringStack(); - break; - case OverviewAnimationType.FULL_ROLL: - springBack(time); - break; - case OverviewAnimationType.TAB_FOCUSED: - // Purposeful fall through - case OverviewAnimationType.NEW_TAB_OPENED: - // Nothing to do. - break; - case OverviewAnimationType.DISCARD_ALL: - mLayout.uiDoneClosingAllTabs(mTabList.isIncognito()); - cleanupStackTabState(); - break; - case OverviewAnimationType.UNDISCARD: - // Purposeful fall through because if UNDISCARD animation updated DISCARD animation, - // DISCARD animation clean up below is not called so UNDISCARD is responsible for - // cleaning it up. - case OverviewAnimationType.DISCARD: - // Remove all dying tabs from mStackTabs. - if (mStackTabs != null) { - // Request for the model to be updated. - for (int i = 0; i < mStackTabs.length; ++i) { - StackTab tab = mStackTabs[i]; - if (tab.isDying()) { - mLayout.uiDoneClosingTab( - time, tab.getId(), true, mTabList.isIncognito()); - } - } - } - cleanupStackTabState(); - break; - default: - break; - } - - // sync the scrollTarget and scrollOffset. For ENTER_STACK animation, don't sync to - // ensure the tab can tilt back. - if (mOverviewAnimationType != OverviewAnimationType.NONE - && mOverviewAnimationType != OverviewAnimationType.ENTER_STACK - && mScroller.isFinished()) { - setScrollTarget(mScrollOffset, true); - } - mOverviewAnimationType = OverviewAnimationType.NONE; - - mStackAnimatorSet = null; - mViewAnimations = null; - } - - private void cleanupStackTabState() { - if (mStackTabs != null) { - // First count the number of tabs that are still alive. - int nNumberOfLiveTabs = 0; - for (int i = 0; i < mStackTabs.length; ++i) { - if (mStackTabs[i].isDying()) { - mLayout.releaseTabLayout(mStackTabs[i].getLayoutTab()); - } else { - nNumberOfLiveTabs++; - } - } - - if (nNumberOfLiveTabs == 0) { - // We have no more live {@link StackTab}. Just clean all tab related states. - cleanupTabs(); - } else if (nNumberOfLiveTabs < mStackTabs.length) { - // If any tabs have died, we need to remove them from mStackTabs. - - StackTab[] oldTabs = mStackTabs; - mStackTabs = new StackTab[nNumberOfLiveTabs]; - - int newIndex = 0; - for (int i = 0; i < oldTabs.length; ++i) { - if (!oldTabs[i].isDying()) { - mStackTabs[newIndex] = oldTabs[i]; - mStackTabs[newIndex].setNewIndex(newIndex); - newIndex++; - } - } - assert newIndex == nNumberOfLiveTabs; - } - } - - mDiscardDirection = getDefaultDiscardDirection(); - } - - /** - * Ensure that there are no dying tabs by finishing the current animation. - * - * @param time The current time of the app in ms. - */ - public void ensureCleaningUpDyingTabs(long time) { - finishAnimation(time); - } - - /** - * Decide if the animation can be started without cleaning up the current animation. - * @param time The current time of the app in ms. - * @param type The type of the animation to start. - * @param sourceIndex The source index needed by some animation types. - * @param finishImmediately Whether the animation jumps straight to the end. - * @return true, if we can start the animation without cleaning up the - * current animation. - */ - private boolean canUpdateAnimation(long time, @OverviewAnimationType int type, int sourceIndex, - boolean finishImmediately) { - if (mAnimationFactory != null) { - if ((mOverviewAnimationType == OverviewAnimationType.DISCARD - || mOverviewAnimationType == OverviewAnimationType.UNDISCARD - || mOverviewAnimationType == OverviewAnimationType.DISCARD_ALL) - && (type == OverviewAnimationType.DISCARD - || type == OverviewAnimationType.UNDISCARD - || type == OverviewAnimationType.DISCARD_ALL)) { - return true; - } - } - return false; - } - - /** - * Cancel scrolling animation which is a part of discarding animation. - * @return true if the animation is canceled, false, if there is nothing to cancel. - */ - private boolean cancelDiscardScrollingAnimation() { - if (mOverviewAnimationType == OverviewAnimationType.DISCARD - || mOverviewAnimationType == OverviewAnimationType.UNDISCARD - || mOverviewAnimationType == OverviewAnimationType.DISCARD_ALL) { - if (mStackAnimatorSet != null) { - mStackAnimatorSet.cancelCancelableAnimators(); - } - return true; - } - return false; - } - - /** - * Checks any Android view animations to see if they have finished yet. - * @param time The current time of the app in ms. - * @param jumpToEnd Whether to finish the animation. - * @return Whether the animation was finished. - */ - public boolean onUpdateViewAnimation(long time, boolean jumpToEnd) { - boolean finished = true; - if (mViewAnimations != null) { - finished = !mViewAnimations.isRunning(); - finishAnimationsIfDone(time, jumpToEnd); - } - return finished; - } - - /** - * Steps the animation forward and updates all the animated values. - * @param time The current time of the app in ms. - * @param jumpToEnd Whether to finish the animation. - * @return Whether the animation was finished. - */ - public boolean onUpdateCompositorAnimations(long time, boolean jumpToEnd) { - if (!jumpToEnd) updateScrollOffset(time); - - boolean animatorSetFinished = true; - if (mStackAnimatorSet != null) { - animatorSetFinished = jumpToEnd ? true : !mStackAnimatorSet.isRunning(); - } - - if (mStackAnimatorSet != null) finishAnimationsIfDone(time, jumpToEnd); - if (jumpToEnd) forceScrollStop(); - - return animatorSetFinished; - } - - private void finishAnimationsIfDone(long time, boolean jumpToEnd) { - boolean hasViewAnimations = mViewAnimations != null; - boolean isViewFinished = hasViewAnimations ? !mViewAnimations.isRunning() : true; - - boolean hasAnimatorSetTabAnimations = mStackAnimatorSet != null; - boolean isAnimatorSetTabFinished = - hasAnimatorSetTabAnimations ? !mStackAnimatorSet.isRunning() : true; - - boolean hasAnimations = hasViewAnimations || hasAnimatorSetTabAnimations; - - boolean shouldFinish = jumpToEnd && hasAnimations; - shouldFinish |= hasAnimations && (!hasViewAnimations || isViewFinished) - && (!hasAnimatorSetTabAnimations || isAnimatorSetTabFinished); - - if (shouldFinish) finishAnimation(time); - } - - /** - * Determines which action was specified by the user's drag. - * - * @param scrollDrag The number of pixels moved in the scroll direction. - * @param discardDrag The number of pixels moved in the discard direction. - * @return The current lock mode or a hint if the motion was not strong enough - * to fully lock the mode. - */ - private @DragLock int computeDragLock(float scrollDrag, float discardDrag) { - scrollDrag = Math.abs(scrollDrag); - discardDrag = Math.abs(discardDrag); - @DragLock - int hintLock = (discardDrag * DRAG_ANGLE_THRESHOLD) > scrollDrag ? DragLock.DISCARD - : DragLock.SCROLL; - // If the user paused the drag for too long, re-determine what the new action is. - long timeMillisecond = System.currentTimeMillis(); - if ((timeMillisecond - mLastScrollUpdate) > DRAG_TIME_THRESHOLD) { - mDragLock = DragLock.NONE; - } - // Select the scroll lock if enough conviction is put into scrolling. - if ((mDragLock == DragLock.NONE && Math.abs(scrollDrag - discardDrag) > mMinScrollMotion) - || (mDragLock == DragLock.DISCARD && discardDrag > mMinScrollMotion) - || (mDragLock == DragLock.SCROLL && scrollDrag > mMinScrollMotion)) { - mLastScrollUpdate = timeMillisecond; - if (mDragLock == DragLock.NONE) { - mDragLock = hintLock; - } - } - // Returns a hint of the lock so we can show feedback even if the lock is not committed - // yet. - return mDragLock == DragLock.NONE ? hintLock : mDragLock; - } - - /* - * User Input Routines: - * - * The input routines that process gestures and click touches. These - * are the main way to interact with the view directly. Other input - * paths happen when model changes impact the view. This can happen - * as a result of some of these actions or from other user input (ie: - * from the Toolbar). These are ignored if an animation is currently - * in progress. - */ - - /** - * Called on drag event (from scroll events in the gesture detector). - * - * @param time The current time of the app in ms. - * @param x The x coordinate of the end of the drag event. - * @param y The y coordinate of the end of the drag event. - * @param amountX The number of pixels dragged in the x direction since the last event. - * @param amountY The number of pixels dragged in the y direction since the last event. - */ - public void drag(long time, float x, float y, float amountX, float amountY) { - float scrollDrag; - float discardDrag; - if (mCurrentMode == Orientation.PORTRAIT) { - discardDrag = amountX; - scrollDrag = amountY; - } else { - discardDrag = amountY; - scrollDrag = LocalizationUtils.isLayoutRtl() ? -amountX : amountX; - } - @DragLock - int hintLock = computeDragLock(scrollDrag, discardDrag); - if (hintLock == DragLock.DISCARD) { - discard(x, y, amountX, amountY); - } else { - // Only cancel the current discard attempt if the scroll lock is committed: - // by using mDragLock instead of hintLock. - if (mDragLock == DragLock.SCROLL && mDiscardingTab != null) { - commitDiscard(time, false); - } - scroll(x, y, LocalizationUtils.isLayoutRtl() ? -amountX : amountX, amountY, false); - } - mLayout.requestUpdate(); - } - - /** - * Discards and updates the position based on the input event values. - * - * @param x The x coordinate of the end of the drag event. - * @param y The y coordinate of the end of the drag event. - * @param amountX The number of pixels dragged in the x direction since the last event. - * @param amountY The number of pixels dragged in the y direction since the last event. - */ - private void discard(float x, float y, float amountX, float amountY) { - if (mStackTabs == null - || (mOverviewAnimationType != OverviewAnimationType.NONE - && mOverviewAnimationType != OverviewAnimationType.DISCARD - && mOverviewAnimationType != OverviewAnimationType.DISCARD_ALL - && mOverviewAnimationType != OverviewAnimationType.UNDISCARD)) { - return; - } - - if (mDiscardingTab == null) { - if (!mInSwipe) { - mDiscardingTab = getTabAtPositon(x, y); - } else { - if (mTabList.index() < 0) return; - mDiscardingTab = mStackTabs[mTabList.index()]; - } - - if (mDiscardingTab != null) { - cancelDiscardScrollingAnimation(); - - // Make sure we are well within the tab in the discard direction. - RectF target = getClickTargetBoundsForLayoutTab(mDiscardingTab.getLayoutTab()); - float distanceToEdge; - float edgeToEdge; - if (mCurrentMode == Orientation.PORTRAIT) { - mDiscardDirection = 1.0f; - distanceToEdge = Math.max(target.left - x, x - target.right); - edgeToEdge = target.width(); - } else { - mDiscardDirection = 2.0f - 4.0f * (x / mLayout.getWidth()); - mDiscardDirection = MathUtils.clamp(mDiscardDirection, -1.0f, 1.0f); - distanceToEdge = Math.max(target.top - y, y - target.bottom); - edgeToEdge = target.height(); - } - - float scaledDiscardX = x - mDiscardingTab.getLayoutTab().getX(); - float scaledDiscardY = y - mDiscardingTab.getLayoutTab().getY(); - mDiscardingTab.setDiscardOriginX(scaledDiscardX / mDiscardingTab.getScale()); - mDiscardingTab.setDiscardOriginY(scaledDiscardY / mDiscardingTab.getScale()); - mDiscardingTab.setDiscardFromClick(false); - - if (Math.abs(distanceToEdge) < DISCARD_SAFE_SELECTION_PCTG * edgeToEdge) { - mDiscardingTab = null; - } - } - } - if (mDiscardingTab != null) { - float deltaAmount = mCurrentMode == Orientation.PORTRAIT ? amountX : amountY; - mDiscardingTab.addToDiscardAmount(deltaAmount); - } - } - - /** - * Called on touch/tilt scroll event. - * - * @param x The x coordinate of the end of the scroll event. - * @param y The y coordinate of the end of the scroll event. - * @param amountX The number of pixels scrolled in the x direction. - * @param amountY The number of pixels scrolled in the y direction. - * @param isTilt True if the call comes from a tilt event. - */ - private void scroll(float x, float y, float amountX, float amountY, boolean isTilt) { - if ((!mScroller.isFinished() && isTilt) || mStackTabs == null - || (mOverviewAnimationType != OverviewAnimationType.NONE - && mOverviewAnimationType != OverviewAnimationType.DISCARD - && mOverviewAnimationType != OverviewAnimationType.UNDISCARD - && mOverviewAnimationType != OverviewAnimationType.DISCARD_ALL - && mOverviewAnimationType != OverviewAnimationType.ENTER_STACK)) { - return; - } - - float amountScreen = mCurrentMode == Orientation.PORTRAIT ? amountY : amountX; - float amountScroll = amountScreen; - float amountEvenOut = amountScreen; - - // Computes the right amount for the scrolling so the finger matches the tab under it. - float tabScrollSpaceFinal = 0; - if (mScrollingTab == null || isTilt) { - mScrollingTab = getTabAtPositon(x, y); - } - - if (mScrollingTab == null && mInSwipe && mStackTabs != null) { - int index = mTabList.index(); - if (index >= 0 && index <= mStackTabs.length) mScrollingTab = mStackTabs[index]; - } - - if (mScrollingTab == null) { - if (!isTilt) { - amountScroll = 0; - amountEvenOut = 0; - } - } else if (mScrollingTab.getIndex() == 0) { - amountEvenOut = 0; - } else { - // Find the scroll that make the selected tab move the right - // amount on the screen. - float tabScrollSpace = mScrollingTab.getScrollOffset() + mScrollOffset; - float tabScreen = scrollToScreen(tabScrollSpace); - tabScrollSpaceFinal = screenToScroll(tabScreen + amountScreen); - amountScroll = tabScrollSpaceFinal - tabScrollSpace; - // Matching the finger is too strong of a constraints on the edges. So we make - // sure the end value is not too far from the linear case. - amountScroll = Math.signum(amountScreen) - * MathUtils.clamp(Math.abs(amountScroll), Math.abs(amountScreen) * 0.5f, - Math.abs(amountScreen) * 2.0f); - } - - // Evens out the tabs and correct the scroll amount if needed. - if (evenOutTabs(amountEvenOut, false) && mScrollingTab.getIndex() > 0) { - // Adjust the amount after the even phase - float tabScrollSpace = mScrollingTab.getScrollOffset() + mScrollOffset; - amountScroll = tabScrollSpaceFinal - tabScrollSpace; - } - - // Actually do the scrolling. - setScrollTarget(mScrollTarget + amountScroll, false); - } - - /** - * OverlappingStack implements this to auto-magically the cards as the stack get scrolled. - * NonOverlappingStack just ignores this call. - * - * @param amount The amount of scroll performed in pixel. The sign indicates - * the direction. - * @param allowReverseDirection Whether or not to allow corrections in the reverse direction - * of the amount scrolled. - * @return True if any tab had been 'visibly' moved. - */ - protected abstract boolean evenOutTabs(float amount, boolean allowReverseDirection); - - /** - * Called on touch fling event. Scroll the stack or help to discard a tab. - * - * @param time The current time of the app in ms. - * @param x The y coordinate of the start of the fling event. - * @param y The y coordinate of the start of the fling event. - * @param velocityX The amount of velocity in the x direction. - * @param velocityY The amount of velocity in the y direction. - */ - public void fling(long time, float x, float y, float velocityX, float velocityY) { - if (mDragLock != DragLock.SCROLL && mDiscardingTab != null) { - float velocity = mCurrentMode == Orientation.PORTRAIT ? velocityX : velocityY; - float maxDelta = getDiscardRange() * DISCARD_FLING_MAX_CONTRIBUTION; - float deltaAmount = MathUtils.clamp(velocity * DISCARD_FLING_DT, -maxDelta, maxDelta); - mDiscardingTab.addToDiscardAmount(deltaAmount); - } else if (mOverviewAnimationType == OverviewAnimationType.NONE && mScroller.isFinished() - && mOverScrollOffset == 0 && getTabIndexAtPositon(x, y) >= 0) { - float velocity = mCurrentMode == Orientation.PORTRAIT - ? velocityY - : (LocalizationUtils.isLayoutRtl() ? -velocityX : velocityX); - // Fling only overscrolls when the stack is fully unfolded. - mScroller.fling(0, (int) mScrollTarget, 0, (int) velocity, 0, 0, - (int) getMinScroll(false), (int) getMaxScroll(false), 0, - (int) ((velocity > 0 ? mMaxOverScroll : mMaxUnderScroll) - * MAX_OVER_FLING_SCALE), - time); - - // Set the target to the final scroll position to make sure - // the offset finally gets there regardless of what happens. - // We override this when the user interrupts the fling though. - setScrollTarget(mScroller.getFinalY(), false); - } - } - - /** - * Get called on down touch event. - * - * @param time The current time of the app in ms. - */ - public void onDown(long time) { - mDragLock = DragLock.NONE; - if (mOverviewAnimationType == OverviewAnimationType.NONE) { - stopScrollingMovement(time); - } - // Resets the scrolling state. - mScrollingTab = null; - commitDiscard(time, false); - } - - /** - * Get called on long press touch event. - * - * @param time The current time of the app in ms. - * @param x The x coordinate in pixel inside the stack view. - * @param y The y coordinate in pixel inside the stack view. - */ - public abstract void onLongPress(long time, float x, float y); - - /** - * Called when at least 2 touch events are detected. - * - * @param time The current time of the app in ms. - * @param x0 The x coordinate of the first touch event. - * @param y0 The y coordinate of the first touch event. - * @param x1 The x coordinate of the second touch event. - * @param y1 The y coordinate of the second touch event. - * @param firstEvent The pinch is the first of a sequence of pinch events. - */ - public abstract void onPinch( - long time, float x0, float y0, float x1, float y1, boolean firstEvent); - - /** - * Commits or release the that currently being considered for discard. This function - * also triggers the associated animations. - * - * @param time The current time of the app in ms. - * @param allowDiscard Whether to allow to discard the tab currently being considered - * for discard. - */ - protected void commitDiscard(long time, boolean allowDiscard) { - if (mDiscardingTab == null) return; - - assert mStackTabs != null; - StackTab discarded = mDiscardingTab; - if (Math.abs(discarded.getDiscardAmount()) / getDiscardRange() > DISCARD_COMMIT_THRESHOLD - && allowDiscard) { - mLayout.uiRequestingCloseTab(time, discarded.getId()); - RecordUserAction.record("MobileStackViewSwipeCloseTab"); - RecordUserAction.record("MobileTabClosed"); - } else { - startAnimation(time, OverviewAnimationType.UNDISCARD); - } - mDiscardingTab = null; - mLayout.requestUpdate(); - } - - /** - * Called on touch up or cancel event. - */ - public void onUpOrCancel(long time) { - // Commit or uncommit discard tab - commitDiscard(time, true); - - resetInputActionIndices(); - - springBack(time); - } - - /** - * Bounces the scroll position back to a valid value (e.g. to correct an overscroll or - * implement snapping). - */ - protected abstract void springBack(long time); - - /** - * Called on touch click event. - * - * @param time The current time of the app in ms. - * @param x The x coordinate in pixel inside the stack view. - * @param y The y coordinate in pixel inside the stack view. - */ - public void click(long time, float x, float y) { - if (mOverviewAnimationType != OverviewAnimationType.NONE - && mOverviewAnimationType != OverviewAnimationType.DISCARD - && mOverviewAnimationType != OverviewAnimationType.UNDISCARD - && mOverviewAnimationType != OverviewAnimationType.DISCARD_ALL) { - return; - } - int clicked = getTabIndexAtPositon(x, y, mCompositorButtonSlop); - if (clicked >= 0) { - // Check if the click was within the boundaries of the close button defined by its - // visible coordinates. - if (checkCloseHitTestOnLayoutTab(x, y, mStackTabs[clicked].getLayoutTab())) { - // Tell the model to close the tab because the close button was pressed. The - // model will then trigger a notification which will start the actual close - // process here if necessary. - StackTab tab = mStackTabs[clicked]; - final float halfCloseBtnWidth = LayoutTab.CLOSE_BUTTON_WIDTH_DP / 2.f; - final float halfCloseBtnHeight = mBorderTopPadding / 2.f; - final float contentWidth = tab.getLayoutTab().getOriginalContentWidth(); - - tab.setDiscardOriginY(halfCloseBtnHeight); - tab.setDiscardOriginX(isCloseButtonOnRight() ? contentWidth - halfCloseBtnWidth - : halfCloseBtnWidth); - tab.setDiscardFromClick(true); - mLayout.uiRequestingCloseTab(time, tab.getId()); - RecordUserAction.record("MobileStackViewCloseTab"); - RecordUserAction.record("MobileTabClosed"); - } else { - // Let the model know that a new {@link LayoutTab} was selected. The model will - // notify us if we need to do anything visual. setIndex() will possibly switch - // the models and broadcast the event. - mLayout.uiSelectingTab(time, mStackTabs[clicked].getId()); - } - } - } - - /** - * Tests if a point is inside the closing button of the tab. - * - * @param x The horizontal coordinate of the hit testing point. - * @param y The vertical coordinate of the hit testing point. - * @param layoutTab The {@link LayoutTab} to test on. - * @return Whether the hit testing point is inside the tab. - */ - @VisibleForTesting - public boolean checkCloseHitTestOnLayoutTab(float x, float y, LayoutTab layoutTab) { - RectF closeRectangle = getCloseBoundsOnLayoutTab(layoutTab); - return closeRectangle != null ? closeRectangle.contains(x, y) : false; - } - - /** - * @param layoutTab The {@link LayoutTab} to check. - * @return The bounds of the {@link LayoutTab} of the close button. {@code null} if the close - * button is not clickable. - */ - @VisibleForTesting - public RectF getCloseBoundsOnLayoutTab(LayoutTab layoutTab) { - if (!layoutTab.get(LayoutTab.IS_TITLE_NEEDED) || !layoutTab.get(LayoutTab.IS_VISIBLE) - || layoutTab.get(LayoutTab.BORDER_ALPHA) < 0.5f - || layoutTab.get(LayoutTab.BORDER_ALPHA) != 1.0f) { - return null; - } - RectF closePlacement = layoutTab.get(LayoutTab.CLOSE_PLACEMENT); - closePlacement.set(0, 0, LayoutTab.CLOSE_BUTTON_WIDTH_DP, LayoutTab.CLOSE_BUTTON_WIDTH_DP); - if (layoutTab.get(LayoutTab.CLOSE_BUTTON_IS_ON_RIGHT)) { - closePlacement.offset(layoutTab.getFinalContentWidth() - closePlacement.width(), 0.f); - } - if (closePlacement.bottom > layoutTab.getFinalContentHeight() - || closePlacement.right > layoutTab.getFinalContentWidth()) { - return null; - } - closePlacement.offset(layoutTab.get(LayoutTab.X) + layoutTab.get(LayoutTab.CLIPPED_X), - layoutTab.get(LayoutTab.Y) + layoutTab.get(LayoutTab.CLIPPED_Y)); - closePlacement.inset(-mCompositorButtonSlop, -mCompositorButtonSlop); - - return closePlacement; - } - - /* - * Initialization and Utility Methods - */ - - /** - * @param context The current Android's context. - */ - public void contextChanged(Context context) { - Resources res = context.getResources(); - final float pxToDp = 1.0f / res.getDisplayMetrics().density; - - mMinScrollMotion = DRAG_MOTION_THRESHOLD_DP; - final float maxOverScrollPx = res.getDimensionPixelOffset(R.dimen.over_scroll); - final float maxUnderScrollPx = Math.round(maxOverScrollPx * MAX_UNDER_SCROLL_SCALE); - mMaxOverScroll = maxOverScrollPx * pxToDp; - mMaxUnderScroll = maxUnderScrollPx * pxToDp; - mMaxOverScrollAngle = res.getInteger(R.integer.over_scroll_angle); - mMaxOverScrollSlide = res.getDimensionPixelOffset(R.dimen.over_scroll_slide) * pxToDp; - mBorderTransparentTop = - res.getDimension(R.dimen.tabswitcher_border_frame_transparent_top) * pxToDp; - mBorderTransparentSide = - res.getDimension(R.dimen.tabswitcher_border_frame_transparent_side) * pxToDp; - mBorderTopPadding = res.getDimension(R.dimen.tabswitcher_border_frame_padding_top) * pxToDp; - mBorderLeftPadding = - res.getDimension(R.dimen.tabswitcher_border_frame_padding_left) * pxToDp; - mCompositorButtonSlop = res.getDimension(R.dimen.compositor_button_slop) * pxToDp; - - // Just in case the density has changed, rebuild the OverScroller. - mScroller = new StackScroller(context); - } - - /** - * @param width The new width of the layout. - * @param height The new height of the layout. - * @param orientation The new orientation of the layout. - */ - public void notifySizeChanged(float width, float height, @Orientation int orientation) { - updateCurrentMode(orientation); - - // Changing the orientation can change which side of the tab we want to show the close - // button on (if the horizontal tab switcher experiment is not enabled). - if (mStackTabs == null) return; - boolean closeButtonIsOnRight = isCloseButtonOnRight(); - for (int i = 0; i < mStackTabs.length; i++) { - mStackTabs[i].getLayoutTab().setCloseButtonIsOnRight(closeButtonIsOnRight); - } - } - - protected float getScrollDimensionSize() { - return mCurrentMode == Orientation.PORTRAIT ? mLayout.getHeightMinusContentOffsetsDp() - : mLayout.getWidth(); - } - - /** - * Gets the tab instance at the requested position. - * - * @param x The x coordinate where to perform the hit test. - * @param y The y coordinate where to perform the hit test. - * @return The instance of the tab selected. null if none. - */ - private StackTab getTabAtPositon(float x, float y) { - int tabIndexAtPosition = getTabIndexAtPositon(x, y, 0); - return tabIndexAtPosition < 0 ? null : mStackTabs[tabIndexAtPosition]; - } - - /** - * Gets the tab index at the requested position. - * - * @param x The x coordinate where to perform the hit test. - * @param y The y coordinate where to perform the hit test. - * @return The index of the tab selected. -1 if none. - */ - protected int getTabIndexAtPositon(float x, float y) { - return getTabIndexAtPositon(x, y, 0); - } - - /** - * Gets the tab index at the requested position. - * - * @param x The x coordinate where to perform the hit test. - * @param y The y coordinate where to perform the hit test. - * @param slop The acceptable distance to a tab for it to be considered. - * @return The index of the tab selected. -1 if none. - */ - private int getTabIndexAtPositon(float x, float y, float slop) { - int closestIndex = -1; - float closestDistance = mLayout.getHeight() + mLayout.getWidth(); - if (mStackTabs != null) { - for (int i = mStackTabs.length - 1; i >= 0; --i) { - // This is a fail safe. We should never have a situation where a dying - // {@link LayoutTab} can get accessed (the animation check should catch it). - if (!mStackTabs[i].isDying() && mStackTabs[i].getLayoutTab().isVisible()) { - float d = computeDistanceToLayoutTab(x, y, mStackTabs[i].getLayoutTab()); - // Strict '<' is very important here because we might have several tab at - // the same place and we want the one above. - if (d < closestDistance) { - closestIndex = i; - closestDistance = d; - if (d == 0) break; - } - } - } - } - return closestDistance <= slop ? closestIndex : -1; - } - - /** - * Computes the Manhattan-ish distance to the edge of the tab. - * This distance is good enough for click detection. - * - * @param x X coordinate of the hit testing point. - * @param y Y coordinate of the hit testing point. - * @param layoutTab The targeting tab. - * @return The Manhattan-ish distance to the tab. - */ - private static float computeDistanceToLayoutTab(float x, float y, LayoutTab layoutTab) { - final RectF bounds = getClickTargetBoundsForLayoutTab(layoutTab); - float dx = Math.max(bounds.left - x, x - bounds.right); - float dy = Math.max(bounds.top - y, y - bounds.bottom); - return Math.max(0.0f, Math.max(dx, dy)); - } - - /** - * @return The rectangle that represents the click target of the tab. - */ - private static RectF getClickTargetBoundsForLayoutTab(LayoutTab layoutTab) { - final float borderScaled = BORDER_THICKNESS_DP * layoutTab.get(LayoutTab.BORDER_SCALE); - RectF bounds = layoutTab.get(LayoutTab.BOUNDS); - bounds.top = layoutTab.get(LayoutTab.Y) + layoutTab.get(LayoutTab.CLIPPED_Y) - borderScaled; - bounds.bottom = layoutTab.get(LayoutTab.Y) + layoutTab.get(LayoutTab.CLIPPED_Y) - + layoutTab.getFinalContentHeight() + borderScaled; - bounds.left = - layoutTab.get(LayoutTab.X) + layoutTab.get(LayoutTab.CLIPPED_X) - borderScaled; - bounds.right = layoutTab.get(LayoutTab.X) + layoutTab.get(LayoutTab.CLIPPED_X) - + layoutTab.getFinalContentWidth() + borderScaled; - return bounds; - } - - /** - * ComputeTabPosition pass 1: - * Combine the overall stack scale with the animated tab scale. - * - * @param stackRect The frame of the stack. - */ - private void computeTabScaleAlphaDepthHelper(RectF stackRect) { - final float stackScale = getStackScale(stackRect); - final float discardRange = getDiscardRange(); - - for (int i = 0; i < mStackTabs.length; ++i) { - assert mStackTabs[i] != null; - StackTab stackTab = mStackTabs[i]; - LayoutTab layoutTab = stackTab.getLayoutTab(); - final float discard = stackTab.getDiscardAmount(); - - // Scale - float discardScale = - computeDiscardScale(discard, discardRange, stackTab.getDiscardFromClick()); - layoutTab.setScale(stackTab.getScale() * discardScale * stackScale); - layoutTab.setBorderScale(discardScale); - - // Alpha - float discardAlpha = computeDiscardAlpha(discard, discardRange); - layoutTab.setAlpha(stackTab.getAlpha() * discardAlpha); - } - } - - /** - * ComputeTabPosition pass 2: - * Adjust the scroll offsets of each tab so no there is no void in between tabs. - */ - private void computeTabScrollOffsetHelper() { - float maxScrollOffset = Float.MAX_VALUE; - for (int i = 0; i < mStackTabs.length; ++i) { - if (mStackTabs[i].isDying()) continue; - - float tabScrollOffset = Math.min(maxScrollOffset, mStackTabs[i].getScrollOffset()); - mStackTabs[i].setScrollOffset(tabScrollOffset); - - float maxScreenScrollOffset = scrollToScreen(mScrollOffset + tabScrollOffset); - maxScrollOffset = -mScrollOffset - + screenToScroll(maxScreenScrollOffset - + mStackTabs[i].getSizeInScrollDirection(mCurrentMode)); - } - } - - /** - * @return Whether or not to enable logic that gives the tabs a "stacked" appearance at the - * top (in portrait mode) or left (in landscape mode). - */ - protected abstract boolean shouldStackTabsAtTop(); - - /** - * @return Whether or not to enable logic that gives the tabs a "stacked" appearance at the - * bottom (in portrait mode) or right (in landscape mode). - */ - protected abstract boolean shouldStackTabsAtBottom(); - - /** - * @return How much the stack should adjust the y position of each LayoutTab in portrait - * mode (as a fraction of the amount space that would be above and below the tab if - * it were centered). - */ - protected abstract float getStackPortraitYOffsetProportion(); - - /** - * @return How much the stack should adjust the x position of each LayoutTab in landscape - * mode (as a fraction of the amount space that would be to the left and right of - * the tab if it were centered). - */ - protected abstract float getStackLandscapeStartOffsetProportion(); - - /** - * @return How much the stack should adjust the x position of each LayoutTab in portrait - * mode (as a fraction of the amount space that would be above and below the tab if - * it were centered). - */ - protected abstract float getStackLandscapeYOffsetProportion(); - - /** - * ComputeTabPosition pass 3: - * Compute the position of the tabs. Adjust for top and bottom stacking. - * - * @param stackRect The frame of the stack. - */ - private void computeTabOffsetHelper(RectF stackRect) { - final boolean portrait = mCurrentMode == Orientation.PORTRAIT; - - // Precompute the position using scroll offset and top stacking. - final float parentWidth = stackRect.width(); - final float parentHeight = stackRect.height(); - final float overscrollPercent = computeOverscrollPercent(); - final float scrollOffset = - MathUtils.clamp(mScrollOffset, getMinScroll(false), getMaxScroll(false)); - final float stackScale = getStackScale(stackRect); - - int stackedCount = 0; - float minStackedPosition = 0.0f; - for (int i = 0; i < mStackTabs.length; ++i) { - assert mStackTabs[i] != null; - StackTab stackTab = mStackTabs[i]; - LayoutTab layoutTab = stackTab.getLayoutTab(); - - // Position - final float stackScrollOffset = - stackTab.isDying() ? mScrollOffsetForDyingTabs : scrollOffset; - float screenScrollOffset = approxScreen(stackTab, stackScrollOffset); - - if (shouldStackTabsAtTop()) { - // Resolve top stacking - screenScrollOffset = Math.max(minStackedPosition, screenScrollOffset); - stackedCount += stackTab.isDying() ? 0 : 1; - if (overscrollPercent < 0) { - // Oversroll at the top of the screen. For the first - // OVERSCROLL_TOP_SLIDE_PCTG of the overscroll, slide the tabs - // together so they completely overlap. After that, stop scrolling the - // tabs. - screenScrollOffset += - (overscrollPercent / OVERSCROLL_TOP_SLIDE_PCTG) * screenScrollOffset; - screenScrollOffset = Math.max(0, screenScrollOffset); - } - } - - // Note: All the Offsets except for centering shouldn't depend on the tab's scaling - // because it interferes the scaling center. - - // Centers the tab in its parent. - float xIn = (parentWidth - layoutTab.getScaledContentWidth()) / 2.0f; - float yIn = (parentHeight - layoutTab.getScaledContentHeight()) / 2.0f; - - // We want slight offset from the center so that multiple tab browsing - // have more space to its expanding direction. e.g., On portrait mode, - // there will be more space on the bottom than top. - final float horizontalPadding = - (parentWidth - - layoutTab.getOriginalContentWidth() * getScaleAmount() * stackScale) - / 2.0f; - final float verticalPadding = - (parentHeight - - layoutTab.getOriginalContentHeight() * getScaleAmount() * stackScale) - / 2.0f; - - if (portrait) { - yIn += getStackPortraitYOffsetProportion() * verticalPadding; - yIn += screenScrollOffset; - } else { - if (LocalizationUtils.isLayoutRtl()) { - xIn -= getStackLandscapeStartOffsetProportion() * horizontalPadding; - xIn -= screenScrollOffset; - } else { - xIn += getStackLandscapeStartOffsetProportion() * horizontalPadding; - xIn += screenScrollOffset; - } - yIn += getStackLandscapeYOffsetProportion() * verticalPadding; - } - - layoutTab.setX(xIn); - layoutTab.setY(yIn); - } - - if (shouldStackTabsAtBottom()) { - // Resolve bottom stacking - stackedCount = 0; - float maxStackedPosition = - portrait ? mLayout.getHeightMinusContentOffsetsDp() : mLayout.getWidth(); - for (int i = mStackTabs.length - 1; i >= 0; i--) { - assert mStackTabs[i] != null; - StackTab stackTab = mStackTabs[i]; - LayoutTab layoutTab = stackTab.getLayoutTab(); - if (stackTab.isDying()) continue; - - float pos; - if (portrait) { - pos = layoutTab.getY(); - layoutTab.setY(Math.min(pos, maxStackedPosition)); - } else if (LocalizationUtils.isLayoutRtl()) { - // On RTL landscape, pos is a distance between tab's right and mLayout's - // right. - float posOffset = mLayout.getWidth() - - layoutTab.getOriginalContentWidth() * getScaleAmount() * stackScale; - pos = -layoutTab.getX() + posOffset; - layoutTab.setX(-Math.min(pos, maxStackedPosition) + posOffset); - } else { - pos = layoutTab.getX(); - layoutTab.setX(Math.min(pos, maxStackedPosition)); - } - if (pos >= maxStackedPosition && stackedCount < MAX_NUMBER_OF_STACKED_TABS_BOTTOM) { - maxStackedPosition -= StackTab.sStackedTabVisibleSize; - stackedCount++; - } - } - } - - // final position blend - final float discardRange = getDiscardRange(); - for (int i = 0; i < mStackTabs.length; ++i) { - assert mStackTabs[i] != null; - StackTab stackTab = mStackTabs[i]; - LayoutTab layoutTab = stackTab.getLayoutTab(); - - final float xIn = layoutTab.getX() + stackTab.getXInStackOffset(); - final float yIn = layoutTab.getY() + stackTab.getYInStackOffset(); - final float xOut = stackTab.getXOutOfStack(); - final float yOut = stackTab.getYOutOfStack(); - float x = MathUtils.interpolate(xOut, xIn, stackTab.getXInStackInfluence()); - float y = MathUtils.interpolate(yOut, yIn, stackTab.getYInStackInfluence()); - - // Discard offsets - if (stackTab.getDiscardAmount() != 0) { - float discard = stackTab.getDiscardAmount(); - boolean fromClick = stackTab.getDiscardFromClick(); - float scale = computeDiscardScale(discard, discardRange, fromClick); - float deltaX = stackTab.getDiscardOriginX() - - stackTab.getLayoutTab().getOriginalContentWidth() / 2.f; - float deltaY = stackTab.getDiscardOriginY() - - stackTab.getLayoutTab().getOriginalContentHeight() / 2.f; - float discardOffset = fromClick ? 0.f : discard; - if (portrait) { - x += discardOffset + deltaX * (1.f - scale); - y += deltaY * (1.f - scale); - } else { - x += deltaX * (1.f - scale); - y += discardOffset + deltaY * (1.f - scale); - } - } - - // Finally apply the stack translation - layoutTab.setX(stackRect.left + x); - layoutTab.setY(stackRect.top + y); - } - } - - /** - * ComputeTabPosition pass 5: - * Computes the clipping, visibility and adjust overall alpha if needed. - */ - protected abstract void computeTabClippingVisibilityHelper(); - - /** - * Computes the index that should be assumed to be the currently centered tab, for purposes - * of prioritizing which thumbnails to render. - */ - protected abstract int computeReferenceIndex(); - - /** - * ComputeTabPosition pass 6: - * Updates the visibility sorting value to use to figure out which thumbnails to load. - * - * @param stackRect The frame of the stack. - */ - private void computeTabVisibilitySortingHelper(RectF stackRect) { - int referenceIndex = mReferenceOrderIndex; - if (referenceIndex == -1) referenceIndex = computeReferenceIndex(); - - final float width = mLayout.getWidth(); - final float height = mLayout.getHeight(); - final float left = MathUtils.clamp(stackRect.left, 0, width); - final float right = MathUtils.clamp(stackRect.right, 0, width); - final float top = MathUtils.clamp(stackRect.top, 0, height); - final float bottom = MathUtils.clamp(stackRect.bottom, 0, height); - final float stackArea = (right - left) * (bottom - top); - final float layoutArea = Math.max(width * height, 1.0f); - final float stackVisibilityMultiplier = stackArea / layoutArea; - - for (int i = 0; i < mStackTabs.length; i++) { - mStackTabs[i].updateStackVisiblityValue(stackVisibilityMultiplier); - mStackTabs[i].updateVisiblityValue(referenceIndex); - } - } - - /** - * Determine the current amount of overscroll. If the value is 0, there is - * no overscroll. If the value is < 0, tabs are overscrolling towards the - * top or or left. If the value is > 0, tabs are overscrolling towards the - * bottom or right. - */ - private float computeOverscrollPercent() { - if (mOverScrollOffset >= 0) { - return mOverScrollOffset / mMaxOverScroll; - } else { - return mOverScrollOffset / mMaxUnderScroll; - } - } - - /** - * Update the tilt of each tab for full roll if necessary. - * - * @param time The current time of the app in ms. - * @param stackRect The frame of the stack. - */ - private void fullRollHelper(long time, RectF stackRect) { - if (mOverviewAnimationType != OverviewAnimationType.FULL_ROLL - && computeOverscrollPercent() < 0 - && mOverScrollCounter >= OVERSCROLL_FULL_ROLL_TRIGGER) { - startAnimation(time, OverviewAnimationType.FULL_ROLL); - mOverScrollCounter = 0; - // Remove overscroll so when the animation finishes the overscroll won't - // be bothering. - setScrollTarget( - MathUtils.clamp(mScrollOffset, getMinScroll(false), getMaxScroll(false)), - false); - } - } - - /** Whether or not to apply logic to enforce that there are no gaps between tabs. */ - protected abstract boolean shouldCloseGapsBetweenTabs(); - - /** - * Computes the {@link LayoutTab} position from the stack and the stackTab data. - * - * @param time The current time of the app in ms. - * @param stackRect The rectangle the stack should be drawn into. It may change over frames. - */ - public void computeTabPosition(long time, RectF stackRect) { - if (mStackTabs == null || mStackTabs.length == 0) return; - - // Step 1: Updates the {@link LayoutTab} scale, alpha and depth values. - computeTabScaleAlphaDepthHelper(stackRect); - - if (shouldCloseGapsBetweenTabs()) { - // Step 2: Fix tab scroll offsets to avoid gaps. - computeTabScrollOffsetHelper(); - } - - // Step 3: Compute the actual position. - computeTabOffsetHelper(stackRect); - - // Step 4: Test if the full-roll animation needs to be run. - fullRollHelper(time, stackRect); - - // Step 5: Clipping, visibility and adjust overall alpha. - computeTabClippingVisibilityHelper(); - - // Step 6: Update visibility sorting for prioritizing thumbnail texture request. - computeTabVisibilitySortingHelper(stackRect); - } - - /** - * @param stackFocus The current amount of focus of the stack [0 .. 1] - * @param orderIndex The index in the stack of the focused tab. -1 to ask the - * stack to compute it. - */ - public void setStackFocusInfo(float stackFocus, int orderIndex) { - if (mStackTabs == null) return; - mReferenceOrderIndex = orderIndex; - } - - /** - * Reverts the closure of the tab specified by {@code tabId}. This will run an undiscard - * animation on that tab. - * @param time The current time of the app in ms. - * @param tabId The id of the tab to animate. - */ - public void undoClosure(long time, int tabId) { - createStackTabs(true); - if (mStackTabs == null) return; - - for (int i = 0; i < mStackTabs.length; i++) { - StackTab tab = mStackTabs[i]; - - if (tab.getId() == tabId) { - tab.setDiscardAmount(getDiscardRange()); - tab.setDying(false); - tab.getLayoutTab().setMaxContentHeight(getMaxTabHeight()); - } - } - - mSpacing = computeSpacing(mStackTabs.length); - startAnimation(time, OverviewAnimationType.UNDISCARD); - } - - /** - * Creates the {@link StackTab}s needed for display and populates {@link #mStackTabs}. - * It is called from show() at the beginning of every new draw phase. It tries to reuse old - * {@link StackTab} instead of creating new ones every time. - * @param restoreState Whether or not to restore the {@link LayoutTab} state when we rebuild - * the {@link StackTab}s. There are some properties like maximum content - * size or whether or not to show the toolbar that might have to be - * restored if we're calling this while the switcher is already visible. - */ - private void createStackTabs(boolean restoreState) { - if (mTabList == null) return; - - final int count = mTabList.getCount(); - if (count == 0) { - cleanupTabs(); - } else { - StackTab[] oldTabs = mStackTabs; - mStackTabs = new StackTab[count]; - - final boolean isIncognito = mTabList.isIncognito(); - for (int i = 0; i < count; ++i) { - Tab tab = mTabList.getTabAt(i); - int tabId = tab != null ? tab.getId() : Tab.INVALID_TAB_ID; - mStackTabs[i] = findTabById(oldTabs, tabId); - - float maxContentWidth = -1.f; - float maxContentHeight = -1.f; - - if (mStackTabs[i] != null && mStackTabs[i].getLayoutTab() != null && restoreState) { - maxContentWidth = mStackTabs[i].getLayoutTab().getMaxContentWidth(); - maxContentHeight = mStackTabs[i].getLayoutTab().getMaxContentHeight(); - } - - LayoutTab layoutTab = mLayout.createLayoutTab( - tabId, isIncognito, maxContentWidth, maxContentHeight); - layoutTab.setInsetBorderVertical(true); - layoutTab.setShowToolbar(true); - layoutTab.setToolbarAlpha(0.f); - layoutTab.setAnonymizeToolbar(!mIsStackForCurrentTabList || mTabList.index() != i); - layoutTab.setCloseButtonIsOnRight(isCloseButtonOnRight()); - - if (mStackTabs[i] == null) { - mStackTabs[i] = new StackTab(layoutTab); - } else { - mStackTabs[i].setLayoutTab(layoutTab); - } - - mStackTabs[i].setNewIndex(i); - // The initial enterStack animation will take care of - // positioning, scaling, etc. - } - } - } - - private StackTab findTabById(StackTab[] layoutTabs, int id) { - if (layoutTabs == null) return null; - final int count = layoutTabs.length; - for (int i = 0; i < count; i++) { - if (layoutTabs[i].getId() == id) return layoutTabs[i]; - } - return null; - } - - /** - * Creates a {@link StackTab}. - * This function should ONLY be called from {@link #tabCreated(long, int)} and nowhere else. - * - * @param id The id of the tab. - * @return Whether the tab has successfully been created and added. - */ - private boolean createTabHelper(int id) { - if (TabModelUtils.getTabById(mTabList, id) == null) return false; - - // Check to see if the tab already exists in our model. This is - // just to cover the case where stackEntered and then tabCreated() - // called in a row. - if (mStackTabs != null) { - final int count = mStackTabs.length; - for (int i = 0; i < count; ++i) { - if (mStackTabs[i].getId() == id) { - return false; - } - } - } - - createStackTabs(true); - - return true; - } - - /** - * @return The percentage of the screen that defines the spacing between tabs by default (no - * pinch). - */ - protected abstract float getSpacingScreen(); - - /** - * This redetermines the proper spacing for the {@link StackTab}. It takes in a parameter - * for the size instead of using the mStackTabs.length property because we could be setting - * the spacing for a delete before the tab has been removed (will help with animations). - * @param layoutTabCount The number of layout tabs currently in the Stack. - * @return How far apart the tabs should be spaced (modulo certain - * adjustments, such as non-linear warping). - */ - protected abstract int computeSpacing(int layoutTabCount); - - private float getStackScale(RectF stackRect) { - return mCurrentMode == Orientation.PORTRAIT - ? stackRect.width() / mLayout.getWidth() - : stackRect.height() / mLayout.getHeightMinusContentOffsetsDp(); - } - - protected void setScrollTarget(float offset, boolean immediate) { - // Ensure that the stack cannot be scrolled too far in either direction. - // mScrollOffset is clamped between [-min, 0], where offset 0 has the - // farthest back tab (the first tab) at the top, with everything else - // pulled down, and -min has the tab at the top of the stack (the last - // tab) is pulled up and fully visible. - final boolean overscroll = allowOverscroll(); - mScrollTarget = MathUtils.clamp(offset, getMinScroll(overscroll), getMaxScroll(overscroll)); - if (immediate) mScrollOffset = mScrollTarget; - mCurrentScrollDirection = Math.signum(mScrollTarget - mScrollOffset); - } - - /** - * Gets the min scroll value. - * - * @param allowUnderScroll True if underscroll is allowed. - */ - protected abstract float getMinScroll(boolean allowUnderScroll); - - /** - * Gets the max scroll value. - * - * @param allowOverscroll True if overscroll is allowed. - */ - protected float getMaxScroll(boolean allowOverscroll) { - if (mStackTabs == null || !allowOverscroll) { - return 0; - } else { - return mMaxOverScroll; - } - } - - private void stopScrollingMovement(long time) { - // We have to cancel the fling if it is in progress. - if (mScroller.computeScrollOffset(time)) { - // Set the current offset and target to the current scroll - // position so the {@link StackTab}s won't scroll anymore. - setScrollTarget(mScroller.getCurrY(), true /* immediate */); - - // Tell the scroller to finish scrolling. - mScroller.forceFinished(true); - } else { - // If we aren't scrolling just set the target to the current - // offset so we don't move anymore. - setScrollTarget(mScrollOffset, false); - } - } - - protected boolean allowOverscroll() { - // All the animations that want to leave the tilt value to be set by the overscroll must - // be added here. - return mOverviewAnimationType == OverviewAnimationType.NONE - || mOverviewAnimationType == OverviewAnimationType.VIEW_MORE - || mOverviewAnimationType == OverviewAnimationType.ENTER_STACK; - } - - /** - * Smoothes input signal. The definition of the input is lower than the - * pixel density of the screen so we need to smooth the input to give the illusion of smooth - * animation on screen from chunky inputs. - * The combination of 20 pixels and 0.9f ensures that the output is not more than 2 pixels - * away from the target. - * TODO: This has nothing to do with time, just draw rate. - * Is this okay or do we want to have the interpolation based on the time elapsed? - * @param current The current value of the signal. - * @param input The raw input value. - * @return The smoothed signal. - */ - private float smoothInput(float current, float input) { - current = MathUtils.clamp(current, input - 20, input + 20); - return MathUtils.interpolate(current, input, 0.9f); - } - - protected void forceScrollStop() { - mScroller.forceFinished(true); - updateOverscrollOffset(); - mScrollTarget = mScrollOffset; - } - - private void updateScrollOffset(long time) { - // If we are still scrolling, which is determined by a disparity - // between our scroll offset and our scroll target, we need - // to try to move closer to that position. - if (mScrollOffset != mScrollTarget) { - if (mScroller.computeScrollOffset(time)) { - final float newScrollOffset = mScroller.getCurrY(); - evenOutTabs(newScrollOffset - mScrollOffset, true); - // We are currently in the process of being flinged. Just - // ask the scroller for the new position. - mScrollOffset = newScrollOffset; - } else { - // We are just being dragged or scrolled, not flinged. This - // means we should move closer to our target quickly but not - // quickly enough to show the stuttering that could be - // exposed by the touch event rate. - mScrollOffset = smoothInput(mScrollOffset, mScrollTarget); - } - mLayout.requestUpdate(); - } else { - // Make sure that the scroller is marked as finished when the destination is - // reached. - mScroller.forceFinished(true); - } - updateOverscrollOffset(); - } - - private void updateOverscrollOffset() { - float clamped = MathUtils.clamp(mScrollOffset, getMinScroll(false), getMaxScroll(false)); - if (!allowOverscroll()) { - mScrollOffset = clamped; - } - float overscroll = mScrollOffset - clamped; - - // Counts the number of overscroll push in the same direction in a row. - int derivativeState = (int) Math.signum(Math.abs(mOverScrollOffset) - Math.abs(overscroll)); - if (derivativeState != mOverScrollDerivative && derivativeState == 1 && overscroll < 0) { - mOverScrollCounter++; - } else if (overscroll > 0 || mCurrentMode == Orientation.LANDSCAPE) { - mOverScrollCounter = 0; - } - mOverScrollDerivative = derivativeState; - - mOverScrollOffset = overscroll; - } - - /** - * Called when the stack is opened to reset all the tab and scroll positions. - */ - protected abstract void resetAllScrollOffset(); - - protected float approxScreen(StackTab tab, float globalScrollOffset) { - return scrollToScreen(tab.getScrollOffset() + globalScrollOffset); - } - - /** - * Maps from scroll coordinates to screen coordinates. - * @param scrollSpace The offset in scroll space. - * @return The offset on screen corresponding to the scroll space offset. - */ - public abstract float scrollToScreen(float scrollSpace); - - /** - * Maps from screen coordinates to scroll coordinates. This allows Stack subclasses (e.g. - * OverlappingStack) to use non-linear scrolling. - * @param scrollSpace The offset in screen space. - * @return The offset in scroll space corresponding to the offset on screen. - */ - public abstract float screenToScroll(float screenSpace); - - /** - * @return The range of the discard action. At the end of the +/- range the discarded tab - * will be fully transparent. - */ - private float getDiscardRange() { - return getRange(DISCARD_RANGE_SCREEN); - } - - private float getRange(float range) { - return range - * (mCurrentMode == Orientation.PORTRAIT ? mLayout.getWidth() - : mLayout.getHeightMinusContentOffsetsDp()); - } - - /** - * @return The maximum height of a layout tab in the tab switcher. - */ - public abstract float getMaxTabHeight(); - - /** - * @return The current spacing between tabs. - */ - public float getSpacing() { - return mSpacing; - } - - /** - * @return The current overall scroll offset for the Stack. - */ - public float getScrollOffset() { - return mScrollOffset; - } - - /** - * Computes the scale of the tab based on its discard status. - * - * @param amount The discard amount. - * @param range The range of the absolute value of discard amount. - * @param fromClick Whether or not the discard was from a click or a swipe. - * @return The scale of the tab to use to draw the tab. - */ - public static float computeDiscardScale(float amount, float range, boolean fromClick) { - if (Math.abs(amount) < 1.0f) return 1.0f; - float t = amount / range; - float endScale = fromClick ? DISCARD_END_SCALE_CLICK : DISCARD_END_SCALE_SWIPE; - return MathUtils.interpolate(1.0f, endScale, Math.abs(t)); - } - - /** - * Computes the alpha value of the tab based on its discard status. - * - * @param amount The discard amount. - * @param range The range of the absolute value of discard amount. - * @return The alpha value that need to be applied on the tab. - */ - public static float computeDiscardAlpha(float amount, float range) { - if (Math.abs(amount) < 1.0f) return 1.0f; - float t = amount / range; - t = MathUtils.clamp(t, -1.0f, 1.0f); - return 1.f - Math.abs(t); - } - - protected void updateCurrentMode(@Orientation int orientation) { - mCurrentMode = orientation; - - mDiscardDirection = getDefaultDiscardDirection(); - final float opaqueTopPadding = mBorderTopPadding - mBorderTransparentTop; - mAnimationFactory = new StackAnimation(this, mLayout.getWidth(), mLayout.getHeight(), - mLayout.getTopContentOffsetDp(), mBorderTopPadding, opaqueTopPadding, - mBorderLeftPadding, mCurrentMode); - mViewAnimationFactory = new StackViewAnimation(mLayout.getContext().getResources()); - if (mStackTabs == null) return; - float width = mLayout.getWidth(); - for (int i = 0; i < mStackTabs.length; i++) { - LayoutTab tab = mStackTabs[i].getLayoutTab(); - if (tab == null) continue; - tab.setMaxContentWidth(width); - tab.setMaxContentHeight(getMaxTabHeight()); - } - } - - /** - * Called to release everything. Called well after the view has been really hidden. - */ - public void cleanupTabs() { - mStackTabs = null; - resetInputActionIndices(); - } - - /** - * Resets all the indices that are pointing to tabs for various features. - */ - protected void resetInputActionIndices() { - mScrollingTab = null; - mDiscardingTab = null; - } - - /** - * Reset session based parameters. - * Called before the a session starts. Before the show, regardless if the stack is - * displayable. - */ - public void reset() { - mIsDying = false; - } - - public static final FloatProperty SCROLL_OFFSET = - new FloatProperty("SCROLL_OFFSET") { - @Override - public void setValue(Stack stack, float v) { - stack.setScrollTarget(v, true); - } - - @Override - public Float get(Stack stack) { - return stack.getScrollOffset(); - } - }; -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java deleted file mode 100644 index c513e5fa38ef..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackAnimation.java +++ /dev/null @@ -1,640 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone.stack; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.TimeInterpolator; - -import androidx.annotation.IntDef; - -import org.chromium.base.MathUtils; -import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; -import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; -import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler; -import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; -import org.chromium.chrome.browser.layouts.animation.FloatProperty; -import org.chromium.ui.base.LocalizationUtils; -import org.chromium.ui.interpolators.BakedBezierInterpolator; -import org.chromium.ui.modelutil.PropertyModel; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - -/** - * A factory that builds animations for the tab stack. - */ -public class StackAnimation { - @IntDef({OverviewAnimationType.ENTER_STACK, OverviewAnimationType.NEW_TAB_OPENED, - OverviewAnimationType.TAB_FOCUSED, OverviewAnimationType.VIEW_MORE, - OverviewAnimationType.REACH_TOP, OverviewAnimationType.DISCARD, - OverviewAnimationType.DISCARD_ALL, OverviewAnimationType.UNDISCARD, - OverviewAnimationType.START_PINCH, OverviewAnimationType.FULL_ROLL, - OverviewAnimationType.NONE}) - @Retention(RetentionPolicy.SOURCE) - public @interface OverviewAnimationType { - int ENTER_STACK = 0; - int NEW_TAB_OPENED = 1; - int TAB_FOCUSED = 2; - int VIEW_MORE = 3; - int REACH_TOP = 4; - // Commit/uncommit tab discard animations - int DISCARD = 5; - int DISCARD_ALL = 6; - int UNDISCARD = 7; - // Start pinch animation un-tilt all the tabs. - int START_PINCH = 8; - // Special animation - int FULL_ROLL = 9; - // Used for when the current state of the system is not animating - int NONE = 10; - } - - private static final int ENTER_STACK_ANIMATION_DURATION_MS = 300; - private static final int ENTER_STACK_BORDER_ALPHA_DURATION_MS = 200; - private static final int ENTER_STACK_RESIZE_DELAY_MS = 10; - private static final int ENTER_STACK_TOOLBAR_ALPHA_DURATION_MS = 100; - private static final int ENTER_STACK_TOOLBAR_ALPHA_DELAY_MS = 100; - private static final float ENTER_STACK_SIZE_RATIO = 0.35f; - - private static final int TAB_FOCUSED_ANIMATION_DURATION_MS = 400; - private static final int TAB_FOCUSED_BORDER_ALPHA_DURATION_MS = 200; - private static final int TAB_FOCUSED_TOOLBAR_ALPHA_DURATION_MS = 250; - private static final int TAB_FOCUSED_Y_STACK_DURATION_MS = 200; - private static final int TAB_FOCUSED_MAX_DELAY_MS = 100; - - private static final int VIEW_MORE_ANIMATION_DURATION_MS = 400; - private static final int VIEW_MORE_MIN_SIZE = 200; - private static final float VIEW_MORE_SIZE_RATIO = 0.75f; - - private static final int REACH_TOP_ANIMATION_DURATION_MS = 400; - - private static final int UNDISCARD_ANIMATION_DURATION_MS = 150; - - private static final int TAB_OPENED_ANIMATION_DURATION_MS = 300; - - private static final int DISCARD_ANIMATION_DURATION_MS = 150; - - private static final int TAB_REORDER_DURATION_MS = 500; - private static final int TAB_REORDER_START_SPAN = 400; - - private static final int START_PINCH_ANIMATION_DURATION_MS = 75; - - private static final int FULL_ROLL_ANIMATION_DURATION_MS = 1000; - - private final float mWidth; - private final float mHeight; - private final float mTopBrowserControlsHeight; - private final float mBorderTopHeight; - private final float mBorderTopOpaqueHeight; - private final float mBorderLeftWidth; - private final Stack mStack; - private final @Orientation int mOrientation; - - /** - * Protected constructor. - * - * @param stack The stack using the animations provided by this class. - * @param width The width of the layout in dp. - * @param height The height of the layout in dp. - * @param heightMinusBrowserControls The height of the layout minus the browser controls in dp. - * @param borderFramePaddingTop The top padding of the border frame in dp. - * @param borderFramePaddingTopOpaque The opaque top padding of the border frame in dp. - * @param borderFramePaddingLeft The left padding of the border frame in dp. - */ - protected StackAnimation(Stack stack, float width, float height, float topBrowserControlsHeight, - float borderFramePaddingTop, float borderFramePaddingTopOpaque, - float borderFramePaddingLeft, @Orientation int orientation) { - mStack = stack; - mWidth = width; - mHeight = height; - mTopBrowserControlsHeight = topBrowserControlsHeight; - mOrientation = orientation; - - mBorderTopHeight = borderFramePaddingTop; - mBorderTopOpaqueHeight = borderFramePaddingTopOpaque; - mBorderLeftWidth = borderFramePaddingLeft; - } - - /** - * This is a wrapper for a {@link AnimatorSet} that plays a set of {@link CompositorAnimator}s - * at the same time, and it has the ability to cancel some {@link CompositorAnimator} animations - * as if it is needed. - */ - class StackAnimatorSet { - private final ArrayList mAnimationList = new ArrayList<>(); - private final AnimatorSet mAnimatorSet = new AnimatorSet(); - private final ArrayList mCancelableAnimators = new ArrayList<>(); - private final CompositorAnimationHandler mHandler; - - StackAnimatorSet(CompositorAnimationHandler handler) { - mHandler = handler; - } - - boolean isPropertyCancelable(FloatProperty property) { - return property == StackTab.SCROLL_OFFSET; - } - - /** - * Helper method to create and add new {@link CompositorAnimator} to the set. - * @param target Target associated with animated property. - * @param property The property being animated. - * @param startValue The starting value of the animation. - * @param endValue The ending value of the animation. - * @param durationMs The duration of the animation. - * @param startTimeMs The start time. - * @param interpolator The time interpolator for the animation. If it is null, will use the - * Interpolators.DECELERATE_INTERPOLATOR. - */ - void addToAnimationWithDelay(final T target, final FloatProperty property, - float startValue, float endValue, long durationMs, long startTimeMs, - TimeInterpolator interpolator) { - CompositorAnimator compositorAnimator; - - if (interpolator == null) { - compositorAnimator = CompositorAnimator.ofFloatProperty( - mHandler, target, property, startValue, endValue, durationMs); - } else { - compositorAnimator = CompositorAnimator.ofFloatProperty( - mHandler, target, property, startValue, endValue, durationMs, interpolator); - } - compositorAnimator.setStartDelay(startTimeMs); - - mAnimationList.add(compositorAnimator); - - if (isPropertyCancelable(property)) mCancelableAnimators.add(compositorAnimator); - } - - void addToAnimation(final T target, final FloatProperty property, float startValue, - float endValue, long durationMs, TimeInterpolator interpolator) { - addToAnimationWithDelay( - target, property, startValue, endValue, durationMs, 0, interpolator); - } - - void addToAnimationWithDelay(final PropertyModel model, - PropertyModel.WritableFloatPropertyKey key, float startValue, float endValue, - long durationMs, long startTimeMs) { - CompositorAnimator compositorAnimator = CompositorAnimator.ofWritableFloatPropertyKey( - mHandler, model, key, startValue, endValue, durationMs); - compositorAnimator.setStartDelay(startTimeMs); - - mAnimationList.add(compositorAnimator); - } - - void addToAnimation(final PropertyModel model, PropertyModel.WritableFloatPropertyKey key, - float startValue, float endValue, long durationMs) { - addToAnimationWithDelay(model, key, startValue, endValue, durationMs, 0); - } - - /** - * Starts the {@link AnimatorSet} animation. - */ - void start() { - mAnimatorSet.playTogether(mAnimationList); - mAnimatorSet.start(); - } - - /** - * Cancels the cancelable animations. - */ - void cancelCancelableAnimators() { - for (int i = 0; i < mCancelableAnimators.size(); i++) { - mCancelableAnimators.get(i).cancel(); - } - } - - /** - * {@see AnimatorSet#isRunning}. - * @return Whether the {@link AnimatorSet} is running. - */ - boolean isRunning() { - return mAnimatorSet.isRunning(); - } - - /** - * Ends the {@link AnimatorSet} animations. - * {@see AnimatorSet#end}. - */ - void end() { - mAnimatorSet.end(); - } - } - - /** - * The wrapper method responsible for delegating the animations request to the appropriate - * helper method. Not all parameters are used for each request. - * - * @param type The type of animation to be created. This is what - * determines which helper method is called. - * @param stack The current stack. - * @param tabs The tabs that make up the current stack that will - * be animated. - * @param focusIndex The index of the tab that is the focus of this animation. - * @param sourceIndex The index of the tab that triggered this animation. - * @param spacing The default spacing between the tabs. - * @param discardRange The range of the discard amount value. - * @return The resulting AnimatorSet that will animate the tabs. - */ - public StackAnimatorSet createAnimatorSetForType(@OverviewAnimationType int type, Stack stack, - StackTab[] tabs, int focusIndex, int sourceIndex, int spacing, float discardRange) { - if (tabs == null) return null; - - StackAnimatorSet stackAnimatorSet = new StackAnimatorSet(stack.getAnimationHandler()); - - switch (type) { - case OverviewAnimationType.DISCARD: // Purposeful fall through - case OverviewAnimationType.DISCARD_ALL: // Purposeful fall through - case OverviewAnimationType.UNDISCARD: - createLandscapePortraitUpdateDiscardAnimatorSet( - stackAnimatorSet, stack, tabs, spacing, discardRange); - break; - case OverviewAnimationType.ENTER_STACK: - // Responsible for generating the animations that shows the stack being entered. - if (mOrientation == Orientation.LANDSCAPE) { - createLandscapeEnterStackAnimatorSet( - stackAnimatorSet, tabs, focusIndex, spacing); - } else { - createPortraitEnterStackAnimatorSet( - stackAnimatorSet, tabs, focusIndex, spacing); - } - break; - case OverviewAnimationType.FULL_ROLL: - break; - case OverviewAnimationType.NEW_TAB_OPENED: - // Responsible for generating the animations that shows a new tab being opened. - if (mOrientation == Orientation.LANDSCAPE) return null; - - for (int i = 0; i < tabs.length; i++) { - stackAnimatorSet.addToAnimation(tabs[i], StackTab.SCROLL_OFFSET, - tabs[i].getScrollOffset(), 0.0f, TAB_OPENED_ANIMATION_DURATION_MS, - null); - } - break; - case OverviewAnimationType.REACH_TOP: - // Responsible for generating the TabSwitcherAnimation that moves the tabs up so - // they reach the to top the screen. - float screenTarget = 0.0f; - for (int i = 0; i < tabs.length; ++i) { - if (screenTarget - >= getLandscapePortraitScreenPositionInScrollDirection(tabs[i])) { - break; - } - stackAnimatorSet.addToAnimation(tabs[i], StackTab.SCROLL_OFFSET, - tabs[i].getScrollOffset(), mStack.screenToScroll(screenTarget), - REACH_TOP_ANIMATION_DURATION_MS, null); - screenTarget += mOrientation == Orientation.LANDSCAPE - ? tabs[i].getLayoutTab().getScaledContentWidth() - : tabs[i].getLayoutTab().getScaledContentHeight(); - } - break; - case OverviewAnimationType.START_PINCH: - break; - case OverviewAnimationType.TAB_FOCUSED: - createLandscapePortraitTabFocusedAnimatorSet( - stackAnimatorSet, tabs, focusIndex, spacing); - break; - case OverviewAnimationType.VIEW_MORE: - // Responsible for generating the animations that Shows more of the selected tab. - if (sourceIndex + 1 >= tabs.length) return null; - - float offset = mOrientation == Orientation.LANDSCAPE - ? tabs[sourceIndex].getLayoutTab().getScaledContentWidth() - : tabs[sourceIndex].getLayoutTab().getScaledContentHeight(); - offset = offset * VIEW_MORE_SIZE_RATIO + tabs[sourceIndex].getScrollOffset() - - tabs[sourceIndex + 1].getScrollOffset(); - offset = Math.max(VIEW_MORE_MIN_SIZE, offset); - - for (int i = sourceIndex + 1; i < tabs.length; ++i) { - stackAnimatorSet.addToAnimation(tabs[i], StackTab.SCROLL_OFFSET, - tabs[i].getScrollOffset(), tabs[i].getScrollOffset() + offset, - VIEW_MORE_ANIMATION_DURATION_MS, null); - } - break; - default: - return null; - } - - return stackAnimatorSet; - } - - private float getLandscapePortraitScreenPositionInScrollDirection(StackTab tab) { - return mOrientation == Orientation.LANDSCAPE ? tab.getLayoutTab().getX() - : tab.getLayoutTab().getY(); - } - - private void createPortraitEnterStackAnimatorSet( - StackAnimatorSet stackAnimatorSet, StackTab[] tabs, int focusIndex, int spacing) { - final float initialScrollOffset = mStack.screenToScroll(0); - - float trailingScrollOffset = 0.f; - if (focusIndex >= 0 && focusIndex < tabs.length - 1) { - final float focusOffset = tabs[focusIndex].getScrollOffset(); - final float nextOffset = tabs[focusIndex + 1].getScrollOffset(); - final float topSpacing = focusIndex == 0 ? spacing : 0.f; - final float extraSpace = tabs[focusIndex].getLayoutTab().getScaledContentHeight() - * ENTER_STACK_SIZE_RATIO; - trailingScrollOffset = Math.max(focusOffset - nextOffset + topSpacing + extraSpace, 0); - } - - for (int i = 0; i < tabs.length; ++i) { - StackTab tab = tabs[i]; - - tab.resetOffset(); - tab.setScale(mStack.getScaleAmount()); - tab.setAlpha(1.f); - tab.getLayoutTab().setToolbarAlpha(i == focusIndex ? 1.f : 0.f); - tab.getLayoutTab().setBorderScale(1.f); - - float scrollOffset = mStack.screenToScroll(i * spacing); - - if (i < focusIndex) { - tab.getLayoutTab().setMaxContentHeight(mStack.getMaxTabHeight()); - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, initialScrollOffset, - scrollOffset, ENTER_STACK_ANIMATION_DURATION_MS, null); - } else if (i > focusIndex) { - tab.getLayoutTab().setMaxContentHeight(mStack.getMaxTabHeight()); - tab.setScrollOffset(scrollOffset + trailingScrollOffset); - stackAnimatorSet.addToAnimation(tab, StackTab.Y_IN_STACK_OFFSET, mHeight, 0, - ENTER_STACK_ANIMATION_DURATION_MS, null); - } else { // i == focusIndex - tab.setScrollOffset(scrollOffset); - - stackAnimatorSet.addToAnimationWithDelay(tab.getLayoutTab(), - LayoutTab.MAX_CONTENT_HEIGHT, - tab.getLayoutTab().getUnclampedOriginalContentHeight(), - mStack.getMaxTabHeight(), ENTER_STACK_ANIMATION_DURATION_MS, - ENTER_STACK_RESIZE_DELAY_MS); - stackAnimatorSet.addToAnimation(tab, StackTab.Y_IN_STACK_INFLUENCE, 0.0f, 1.0f, - ENTER_STACK_BORDER_ALPHA_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab, StackTab.SCALE, 1.0f, mStack.getScaleAmount(), - ENTER_STACK_BORDER_ALPHA_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.TOOLBAR_Y_OFFSET, 0.f, - getToolbarOffsetToLineUpWithBorder(), ENTER_STACK_BORDER_ALPHA_DURATION_MS); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.SIDE_BORDER_SCALE, - 0.f, 1.f, ENTER_STACK_BORDER_ALPHA_DURATION_MS); - - stackAnimatorSet.addToAnimationWithDelay(tab.getLayoutTab(), - LayoutTab.TOOLBAR_ALPHA, 1.f, 0.f, ENTER_STACK_BORDER_ALPHA_DURATION_MS, - ENTER_STACK_TOOLBAR_ALPHA_DELAY_MS); - - tab.setYOutOfStack(getStaticTabPosition()); - } - } - } - - private void createLandscapeEnterStackAnimatorSet( - StackAnimatorSet stackAnimatorSet, StackTab[] tabs, int focusIndex, int spacing) { - final float initialScrollOffset = mStack.screenToScroll(0); - - for (int i = 0; i < tabs.length; ++i) { - StackTab tab = tabs[i]; - - tab.resetOffset(); - tab.setScale(mStack.getScaleAmount()); - tab.setAlpha(1.f); - tab.getLayoutTab().setToolbarAlpha(i == focusIndex ? 1.f : 0.f); - tab.getLayoutTab().setBorderScale(1.f); - - final float scrollOffset = mStack.screenToScroll(i * spacing); - - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.MAX_CONTENT_HEIGHT, - tab.getLayoutTab().getUnclampedOriginalContentHeight(), - mStack.getMaxTabHeight(), ENTER_STACK_ANIMATION_DURATION_MS); - if (i < focusIndex) { - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, initialScrollOffset, - scrollOffset, ENTER_STACK_ANIMATION_DURATION_MS, null); - } else if (i > focusIndex) { - tab.setScrollOffset(scrollOffset); - stackAnimatorSet.addToAnimation(tab, StackTab.X_IN_STACK_OFFSET, - (mWidth > mHeight && LocalizationUtils.isLayoutRtl()) ? -mWidth : mWidth, - 0.0f, ENTER_STACK_ANIMATION_DURATION_MS, null); - } else { // i == focusIndex - tab.setScrollOffset(scrollOffset); - - stackAnimatorSet.addToAnimation(tab, StackTab.X_IN_STACK_INFLUENCE, 0.0f, 1.0f, - ENTER_STACK_BORDER_ALPHA_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab, StackTab.SCALE, 1.0f, mStack.getScaleAmount(), - ENTER_STACK_BORDER_ALPHA_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.TOOLBAR_Y_OFFSET, 0.f, - getToolbarOffsetToLineUpWithBorder(), ENTER_STACK_BORDER_ALPHA_DURATION_MS); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.SIDE_BORDER_SCALE, - 0.f, 1.f, ENTER_STACK_BORDER_ALPHA_DURATION_MS); - - stackAnimatorSet.addToAnimationWithDelay(tab.getLayoutTab(), - LayoutTab.TOOLBAR_ALPHA, 1.f, 0.f, ENTER_STACK_TOOLBAR_ALPHA_DURATION_MS, - ENTER_STACK_TOOLBAR_ALPHA_DELAY_MS); - } - } - } - - /** - * Responsible for generating the animations that shows a tab being - * focused (the stack is being left). - * @param stackAnimatorSet {@link StackAnimatorSet} for created animations. - * @param tabs The tabs that make up the stack. These are the - * tabs that will be affected by the TabSwitcherAnimation. - * @param focusIndex The focused index. In this case, this is the index of - * the tab clicked and is being brought up to view. - * @param spacing The default spacing between tabs. - */ - private void createLandscapePortraitTabFocusedAnimatorSet( - StackAnimatorSet stackAnimatorSet, StackTab[] tabs, int focusIndex, int spacing) { - for (int i = 0; i < tabs.length; ++i) { - StackTab tab = tabs[i]; - LayoutTab layoutTab = tab.getLayoutTab(); - - stackAnimatorSet.addToAnimation(tab, StackTab.DISCARD_AMOUNT, tab.getDiscardAmount(), - 0.0f, TAB_FOCUSED_ANIMATION_DURATION_MS, null); - - if (i < focusIndex) { - // Landscape: for tabs left of the focused tab move them left to 0. - // Portrait: for tabs above the focused tab move them up to 0. - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, tab.getScrollOffset(), - mOrientation == Orientation.LANDSCAPE - ? Math.max(0.0f, tab.getScrollOffset() - mWidth - spacing) - : tab.getScrollOffset() - mHeight - spacing, - TAB_FOCUSED_ANIMATION_DURATION_MS, null); - continue; - } else if (i > focusIndex) { - if (mOrientation == Orientation.LANDSCAPE) { - // We also need to animate the X Translation to move them right - // off the screen. - float coveringTabPosition = layoutTab.getX(); - float distanceToBorder = LocalizationUtils.isLayoutRtl() - ? coveringTabPosition + layoutTab.getScaledContentWidth() - : mWidth - coveringTabPosition; - float clampedDistanceToBorder = MathUtils.clamp(distanceToBorder, 0, mWidth); - float delay = TAB_FOCUSED_MAX_DELAY_MS * clampedDistanceToBorder / mWidth; - stackAnimatorSet.addToAnimationWithDelay(tab, StackTab.X_IN_STACK_OFFSET, - tab.getXInStackOffset(), - tab.getXInStackOffset() - + (LocalizationUtils.isLayoutRtl() ? -mWidth : mWidth), - (TAB_FOCUSED_ANIMATION_DURATION_MS - (long) delay), (long) delay, null); - } else { // mOrientation == Orientation.PORTRAIT - // We also need to animate the Y Translation to move them down - // off the screen. - float coveringTabPosition = layoutTab.getY(); - float distanceToBorder = - MathUtils.clamp(mHeight - coveringTabPosition, 0, mHeight); - float delay = TAB_FOCUSED_MAX_DELAY_MS * distanceToBorder / mHeight; - stackAnimatorSet.addToAnimationWithDelay(tab, StackTab.Y_IN_STACK_OFFSET, - tab.getYInStackOffset(), tab.getYInStackOffset() + mHeight, - (TAB_FOCUSED_ANIMATION_DURATION_MS - (long) delay), (long) delay, null); - } - continue; - } - - // This is the focused tab. We need to scale it back to - // 1.0f, move it to the top of the screen, and animate the - // X Translation (for Landscape) / Y Translation (for Portrait) so that it looks like it - // is zooming into the full screen view. - // - // In Landscape we additionally move the card to the top left and extend it out so it - // becomes a full card. - tab.setXOutOfStack(0); - tab.setYOutOfStack(0.0f); - layoutTab.setBorderScale(1.f); - - if (mOrientation == Orientation.LANDSCAPE) { - stackAnimatorSet.addToAnimation(tab, StackTab.X_IN_STACK_INFLUENCE, - tab.getXInStackInfluence(), 0.0f, TAB_FOCUSED_ANIMATION_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, tab.getScrollOffset(), - mStack.screenToScroll(0), TAB_FOCUSED_ANIMATION_DURATION_MS, null); - } else { // mOrientation == Orientation.PORTRAIT - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, tab.getScrollOffset(), - Math.max(0.0f, tab.getScrollOffset() - mWidth - spacing), - TAB_FOCUSED_ANIMATION_DURATION_MS, null); - } - - stackAnimatorSet.addToAnimation(tab, StackTab.SCALE, tab.getScale(), 1.0f, - TAB_FOCUSED_ANIMATION_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab, StackTab.Y_IN_STACK_INFLUENCE, - tab.getYInStackInfluence(), 0.0f, TAB_FOCUSED_Y_STACK_DURATION_MS, null); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.MAX_CONTENT_HEIGHT, - tab.getLayoutTab().getMaxContentHeight(), - tab.getLayoutTab().getUnclampedOriginalContentHeight(), - TAB_FOCUSED_ANIMATION_DURATION_MS); - - tab.setYOutOfStack(getStaticTabPosition()); - - if (layoutTab.shouldStall()) { - stackAnimatorSet.addToAnimation(layoutTab, LayoutTab.SATURATION, 1.0f, 0.0f, - TAB_FOCUSED_BORDER_ALPHA_DURATION_MS); - } - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.TOOLBAR_ALPHA, - layoutTab.getToolbarAlpha(), 1.f, TAB_FOCUSED_TOOLBAR_ALPHA_DURATION_MS); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.TOOLBAR_Y_OFFSET, - getToolbarOffsetToLineUpWithBorder(), 0.f, - TAB_FOCUSED_TOOLBAR_ALPHA_DURATION_MS); - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.SIDE_BORDER_SCALE, 1.f, - 0.f, TAB_FOCUSED_TOOLBAR_ALPHA_DURATION_MS); - } - } - - /** - * Responsible for generating the animations that moves the tabs back in from - * discard attempt or commit the current discard (if any). It also re-even the tabs - * if one of then is removed. - * @param stackAnimatorSet {@link StackAnimatorSet} for created animations. - * @param stack Stack. - * @param tabs The tabs that make up the stack. These are the - * tabs that will be affected by the TabSwitcherAnimation. - * @param spacing The default spacing between tabs. - * @param discardRange The maximum value the discard amount. - */ - private void createLandscapePortraitUpdateDiscardAnimatorSet(StackAnimatorSet stackAnimatorSet, - Stack stack, StackTab[] tabs, int spacing, float discardRange) { - int dyingTabsCount = 0; - int firstDyingTabIndex = -1; - float firstDyingTabOffset = 0; - for (int i = 0; i < tabs.length; ++i) { - if (tabs[i].isDying()) { - dyingTabsCount++; - if (dyingTabsCount == 1) { - firstDyingTabIndex = i; - firstDyingTabOffset = - getLandscapePortraitScreenPositionInScrollDirection(tabs[i]); - } - } - } - - float screenSizeInScrollDirection = - mOrientation == Orientation.LANDSCAPE ? mWidth : mHeight; - - // This is used to determine the discard direction when user just clicks X to close a - // tab. On portrait, positive direction (x) is right hand side (on clicking the close - // button, discard the tab to the right on LTR, to the left on RTL). On landscape, - // positive direction (y) is towards bottom. - boolean defaultDiscardDirectionPositive = - mOrientation == Orientation.LANDSCAPE ? true : !LocalizationUtils.isLayoutRtl(); - - int newIndex = 0; - for (int i = 0; i < tabs.length; ++i) { - StackTab tab = tabs[i]; - // If the non-overlapping horizontal tab switcher is enabled, we shift all the - // tabs over simultaneously. Otherwise we stagger the animation start times to - // create a ripple effect. - long startTime = (long) Math.max(0, - TAB_REORDER_START_SPAN / screenSizeInScrollDirection - * (getLandscapePortraitScreenPositionInScrollDirection(tab) - - firstDyingTabOffset)); - if (tab.isDying()) { - float discard = tab.getDiscardAmount(); - if (discard == 0.0f) discard = defaultDiscardDirectionPositive ? 0.0f : -0.0f; - float s = Math.copySign(1.0f, discard); - long duration = (long) (DISCARD_ANIMATION_DURATION_MS - * (1.0f - Math.abs(discard / discardRange))); - - stackAnimatorSet.addToAnimation(tab, StackTab.DISCARD_AMOUNT, discard, - discardRange * s, duration, BakedBezierInterpolator.FADE_OUT_CURVE); - } else { - if (tab.getDiscardAmount() != 0.f) { - stackAnimatorSet.addToAnimation(tab, StackTab.DISCARD_AMOUNT, - tab.getDiscardAmount(), 0.0f, UNDISCARD_ANIMATION_DURATION_MS, null); - } - stackAnimatorSet.addToAnimation(tab, StackTab.SCALE, tab.getScale(), - mStack.getScaleAmount(), DISCARD_ANIMATION_DURATION_MS, null); - - stackAnimatorSet.addToAnimation(tab.getLayoutTab(), LayoutTab.MAX_CONTENT_HEIGHT, - tab.getLayoutTab().getMaxContentHeight(), mStack.getMaxTabHeight(), - DISCARD_ANIMATION_DURATION_MS); - - float newScrollOffset = mStack.screenToScroll(spacing * newIndex); - - // If the tab is not dying we want to readjust it's position - // based on the new spacing requirements. For a fully discarded tab, just - // put it in the right place. - if (tab.getDiscardAmount() >= discardRange) { - tab.setScrollOffset(newScrollOffset); - tab.setScale(mStack.getScaleAmount()); - } else { - float start = tab.getScrollOffset(); - if (start != newScrollOffset) { - stackAnimatorSet.addToAnimation(tab, StackTab.SCROLL_OFFSET, start, - newScrollOffset, TAB_REORDER_DURATION_MS, null); - } - } - newIndex++; - } - } - } - - /** - * @return The offset for the toolbar to line the top up with the opaque component of - * the border. - */ - private float getToolbarOffsetToLineUpWithBorder() { - return mTopBrowserControlsHeight - mBorderTopOpaqueHeight; - } - - /** - * @return The position of the static tab when entering or exiting the tab switcher. - */ - private float getStaticTabPosition() { - return mTopBrowserControlsHeight - mBorderTopHeight; - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackTab.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackTab.java deleted file mode 100644 index 3b64ff0cf459..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackTab.java +++ /dev/null @@ -1,524 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone.stack; - -import android.content.Context; -import android.content.res.Resources; - -import org.chromium.chrome.R; -import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; -import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; -import org.chromium.chrome.browser.layouts.animation.FloatProperty; - -/** - * StackTab is used to keep track of a thumbnail's bitmap and position and to - * draw itself onto the GL canvas at the desired Y Offset. - * @VisibleForTesting - */ -public class StackTab { - private static final float ALPHA_THRESHOLD = 1.0f / 255.0f; - // Cached values from values/dimens.xml - public static float sStackedTabVisibleSize; // stacked_tab_visible_size - public static float sStackBufferWidth; // stack_buffer_width - public static float sStackBufferHeight; // stack_buffer_height - - // Positioner selector - private float mXInStackInfluence = 1.0f; - private float mYInStackInfluence = 1.0f; - - // In stack positioner - private float mScrollOffset; - private float mXInStackOffset; - private float mYInStackOffset; - - // Out of stack positioner - private float mXOutOfStack; - private float mYOutOfStack; - - // Values that get animated - private float mAlpha = 1.0f; - private float mScale = 1.0f; - private float mDiscardAmount; // This might alter position, rotation and alpha - - // Discard states - private float mDiscardOriginX; - private float mDiscardOriginY; - private boolean mDiscardFromClick; - - // The index of the tab in the stack - private int mIndex; - - // True if the tab is currently being removed (while animating). - protected boolean mDying; - - // The visibility sorting value is used to determine the importance of the tab for - // texture allocation. It is computed from the area and its position in the stack. - // Larger values will have more priority for acquiring texture. Negative values "often" - // means that the tab is not visible at all (but there are no guaranty and it's fine). - private float mCachedVisibleArea; // Intermediate value - private float mCachedIndexDistance; // Intermediate value - private float mCacheStackVisibility = 1.0f; // Intermediate value - private long mVisiblitySortingValue; // Sorting value based on visible area. - private int mOrderSortingValue; // Sorting value based on distance to selection. - - private LayoutTab mLayoutTab; - - public static final FloatProperty DISCARD_AMOUNT = - new FloatProperty("DISCARD_AMOUNT") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setDiscardAmount(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getDiscardAmount(); - } - }; - - public static final FloatProperty SCALE = new FloatProperty("SCALE") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setScale(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getScale(); - } - }; - - public static final FloatProperty SCROLL_OFFSET = - new FloatProperty("SCROLL_OFFSET") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setScrollOffset(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getScrollOffset(); - } - }; - - public static final FloatProperty X_IN_STACK_INFLUENCE = - new FloatProperty("X_IN_STACK_INFLUENCE") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setXInStackInfluence(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getXInStackInfluence(); - } - }; - - public static final FloatProperty X_IN_STACK_OFFSET = - new FloatProperty("X_IN_STACK_OFFSET") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setXInStackOffset(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getXInStackOffset(); - } - }; - - public static final FloatProperty Y_IN_STACK_INFLUENCE = - new FloatProperty("Y_IN_STACK_INFLUENCE") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setYInStackInfluence(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getYInStackInfluence(); - } - }; - - public static final FloatProperty Y_IN_STACK_OFFSET = - new FloatProperty("Y_IN_STACK_OFFSET") { - @Override - public void setValue(StackTab layoutTab, float v) { - layoutTab.setYInStackOffset(v); - } - - @Override - public Float get(StackTab layoutTab) { - return layoutTab.getYInStackOffset(); - } - }; - - /** - * @param tab The tab this instance is supposed to draw. - */ - public StackTab(LayoutTab tab) { - mLayoutTab = tab; - } - - /** - * @param index The new index in the stack layout. - */ - public void setNewIndex(int index) { - mIndex = index; - } - - /** - * @return The index in the stack layout. - */ - public int getIndex() { - return mIndex; - } - - /** - * @return The {@link LayoutTab} this instance is supposed to draw. - */ - public LayoutTab getLayoutTab() { - return mLayoutTab; - } - - /** - * Set the {@link LayoutTab} this instance is supposed to draw. - */ - public void setLayoutTab(LayoutTab tab) { - mLayoutTab = tab; - } - - /** - * @return The id of the tab, same as the id from the Tab in TabModel. - */ - public int getId() { - return mLayoutTab.getId(); - } - - /** - * @param y The vertical translation to be applied after the placement in the stack. - */ - public void setYInStackOffset(float y) { - mYInStackOffset = y; - } - - /** - * @return The vertical translation applied after the placement in the stack. - */ - public float getYInStackOffset() { - return mYInStackOffset; - } - - /** - * @param x The horizontal translation to be applied after the placement in the stack. - */ - public void setXInStackOffset(float x) { - mXInStackOffset = x; - } - - /** - * @return The horizontal translation applied after the placement in the stack. - */ - public float getXInStackOffset() { - return mXInStackOffset; - } - - /** - * @param y The vertical absolute position when out of stack. - */ - public void setYOutOfStack(float y) { - mYOutOfStack = y; - } - - /** - * @return The vertical absolute position when out of stack. - */ - public float getYOutOfStack() { - return mYOutOfStack; - } - - /** - * @param x The horizontal absolute position when out of stack. - */ - public void setXOutOfStack(float x) { - mXOutOfStack = x; - } - - /** - * @return The horizontal absolute position when out of stack. - */ - public float getXOutOfStack() { - return mXOutOfStack; - } - - /** - * Set the transparency value for all of the tab (the contents, - * border, etc...). For components that allow specifying - * their own alpha values, it will use the min of these two fields. - * - * @param f The transparency value for the tab. - */ - public void setAlpha(float f) { - mAlpha = f; - } - - /** - * @return The transparency value for all of the tab components. - */ - public float getAlpha() { - return mAlpha; - } - - /** - * @param xInStackInfluence The horizontal blend value between instack - * and out of stack pacement [0 .. 1]. - */ - public void setXInStackInfluence(float xInStackInfluence) { - mXInStackInfluence = xInStackInfluence; - } - - /** - * @return The horizontal blend value between instack and out of stack pacement [0 .. 1]. - */ - public float getXInStackInfluence() { - return mXInStackInfluence; - } - - /** - * @param yInStackInfluence The vertical blend value between instack - * and out of stack pacement [0 .. 1]. - */ - public void setYInStackInfluence(float yInStackInfluence) { - mYInStackInfluence = yInStackInfluence; - } - - /** - * @return The verical blend value between instack and out of stack pacement [0 .. 1]. - */ - public float getYInStackInfluence() { - return mYInStackInfluence; - } - - /** - * @param scale The scale to apply to the tab, compared to the parent. - */ - public void setScale(float scale) { - mScale = scale; - } - - /** - * @return The scale to apply to the tab, compared to the parent. - */ - public float getScale() { - return mScale; - } - - /** - * @param offset The offset of the tab along the scrolling direction in scroll space. - */ - public void setScrollOffset(float offset) { - mScrollOffset = offset; - } - - /** - * @return The offset of the tab along the scrolling direction in scroll space. - */ - public float getScrollOffset() { - return mScrollOffset; - } - - /** - * @param amount The amount of discard displacement. 0 is no discard. Negative is discard - * on the left. Positive is discard on the right. - */ - public void setDiscardAmount(float amount) { - mDiscardAmount = amount; - } - - /** - * @param deltaAmount The amount of delta discard to be added to the current discard amount. - */ - public void addToDiscardAmount(float deltaAmount) { - mDiscardAmount += deltaAmount; - } - - /** - * @return The amount of discard displacement. 0 is no discard. Negative is discard - * on the left. Positive is discard on the right. - */ - public float getDiscardAmount() { - return mDiscardAmount; - } - - /** - * @param x The x coordinate in tab space of where the discard transforms should originate. - */ - public void setDiscardOriginX(float x) { - mDiscardOriginX = x; - } - - /** - * @param y The y coordinate in tab space of where the discard transforms should originate. - */ - public void setDiscardOriginY(float y) { - mDiscardOriginY = y; - } - - /** - * @return The x coordinate in tab space of where the discard transforms should originate. - */ - public float getDiscardOriginX() { - return mDiscardOriginX; - } - - /** - * @return The y coordinate in tab space of where the discard transforms should originate. - */ - public float getDiscardOriginY() { - return mDiscardOriginY; - } - - /** - * @param fromClick Whether or not this discard was from a click event. - */ - public void setDiscardFromClick(boolean fromClick) { - mDiscardFromClick = fromClick; - } - - /** - * @return Whether or not this discard was from a click event. - */ - public boolean getDiscardFromClick() { - return mDiscardFromClick; - } - - /** - * @param dying True if the Tab/ContentView will be destroyed, and we are still animating its - * visible representation. - */ - public void setDying(boolean dying) { - mDying = dying; - } - - /** - * @return True if the Tab/ContentView is destroyed, but we are still animating its - * visible representation. - */ - public boolean isDying() { - return mDying; - } - - /** - * @param orientation The orientation to choose to get the size. - * @return The size of the content along the provided orientation. - */ - public float getSizeInScrollDirection(@Orientation int orientation) { - if (orientation == Orientation.PORTRAIT) { - return mLayoutTab.getScaledContentHeight(); - } else { - return mLayoutTab.getScaledContentWidth(); - } - } - - /** - * Helper function that gather the static constants from values/dimens.xml. - * @param context The Android Context. - */ - public static void resetDimensionConstants(Context context) { - Resources res = context.getResources(); - final float pxToDp = 1.0f / res.getDisplayMetrics().density; - sStackedTabVisibleSize = - res.getDimensionPixelOffset(R.dimen.stacked_tab_visible_size) * pxToDp; - sStackBufferWidth = res.getDimensionPixelOffset(R.dimen.stack_buffer_width) * pxToDp; - sStackBufferHeight = res.getDimensionPixelOffset(R.dimen.stack_buffer_height) * pxToDp; - } - - /** - * Reset the offset to factory default. - */ - public void resetOffset() { - mXInStackInfluence = 1.0f; - mYInStackInfluence = 1.0f; - mScrollOffset = 0.0f; - mXInStackOffset = 0.0f; - mYInStackOffset = 0.0f; - mXOutOfStack = 0.0f; - mYOutOfStack = 0.0f; - mDiscardOriginX = 0.f; - mDiscardOriginY = 0.f; - mDiscardFromClick = false; - } - - /** - * Updates the cached visible area value to be used to sort tabs by visibility. - * @param referenceIndex The index that has the highest priority. - */ - public void updateVisiblityValue(int referenceIndex) { - mCachedVisibleArea = computeVisibleArea(); - mCachedIndexDistance = Math.abs(mIndex - referenceIndex); - mOrderSortingValue = computeOrderSortingValue(mCachedIndexDistance, mCacheStackVisibility); - mVisiblitySortingValue = computeVisibilitySortingValue( - mCachedVisibleArea, mOrderSortingValue, mCacheStackVisibility); - } - - /** - * @return The theoretical number of visible pixels. 0 if invisible. - */ - private float computeVisibleArea() { - return (mLayoutTab.get(LayoutTab.IS_VISIBLE) - && mLayoutTab.get(LayoutTab.ALPHA) > ALPHA_THRESHOLD - ? 1.0f - : 0.0f) - * mLayoutTab.getFinalContentWidth() * mLayoutTab.getFinalContentHeight(); - } - - /** - * Updates the cached visible area value to be used to sort tabs by visibility. - * @param stackVisibility Multiplier that represents how much the stack fills the screen. - */ - public void updateStackVisiblityValue(float stackVisibility) { - mCacheStackVisibility = stackVisibility; - mOrderSortingValue = computeOrderSortingValue(mCachedIndexDistance, mCacheStackVisibility); - mVisiblitySortingValue = computeVisibilitySortingValue( - mCachedVisibleArea, mOrderSortingValue, mCacheStackVisibility); - } - - /** - * Computes the visibility sorting value based on the tab visible area, its distance to the - * central index and the overall visibility of the stack. - * The '-index' index factor need to be smaller for stack that have small visibility. - * Multiplying by a small stackVisibility makes it bigger (because it is negative), hence the - * division. To avoid dividing by 0 it need to be offset a bit. 0.1f is the 'a bit' part of - * the explanation. - */ - private static long computeVisibilitySortingValue( - float area, float orderSortingValue, float stackVisibility) { - return (long) (area * stackVisibility - orderSortingValue); - } - - /** - * @return The cached visible sorting value. Call updateCachedVisibleArea to update it. - */ - public long getVisiblitySortingValue() { - return mVisiblitySortingValue; - } - - /** - * Computes the ordering value only based on the distance of the tab to the center one. - * Low values have higher priority. - */ - private static int computeOrderSortingValue(float indexDistance, float stackVisibility) { - return (int) ((indexDistance + 1) / (0.1f + 0.9f * stackVisibility)); - } - - /** - * @return The cached order sorting value. Used to sort based on the tab ordering rather than - * visible area. - */ - public int getOrderSortingValue() { - return mOrderSortingValue; - } -} diff --git a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackViewAnimation.java b/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackViewAnimation.java deleted file mode 100644 index 648cca339aec..000000000000 --- a/android/java/org/chromium/chrome/browser/compositor/layouts/phone/stack/StackViewAnimation.java +++ /dev/null @@ -1,110 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.chromium.chrome.browser.compositor.layouts.phone.stack; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.content.res.Resources; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.widget.FrameLayout; - -import org.chromium.chrome.R; -import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackAnimation.OverviewAnimationType; -import org.chromium.chrome.browser.tab.Tab; -import org.chromium.chrome.browser.tabmodel.TabList; -import org.chromium.chrome.browser.theme.ThemeUtils; -import org.chromium.components.browser_ui.widget.animation.Interpolators; - -/** - * A factory that builds Android view animations for the tab stack. - */ -public class StackViewAnimation { - private static final int TAB_OPENED_BG_ANIMATION_DURATION = 150; - private static final int TAB_OPENED_VIEW_ANIMATION_DURATION = 350; - - private final int mTranslationYStart; - - /** - * Constructor. - * - * @param resources Android {@link Resources} used to retrieve dimensions. - */ - public StackViewAnimation(Resources resources) { - mTranslationYStart = - resources.getDimensionPixelSize(R.dimen.open_new_tab_animation_y_translation); - } - - /** - * The wrapper method responsible for delegating animation requests to the appropriate helper - * method. - * @param type The type of animation to be created. This is what determines which helper - * method is called. - * @param tabs The tabs that make up the current stack. - * @param container The {@link ViewGroup} that {@link View}s can be added to/removed from. - * @param list The {@link TabList} that this animation will influence. - * @param focusIndex The index of the tab that is the focus of this animation. - * @return The resulting {@link Animator} that will animate the Android views. - */ - public Animator createAnimatorForType(@OverviewAnimationType int type, StackTab[] tabs, - ViewGroup container, TabList list, int focusIndex) { - Animator animator = null; - - if (list != null && type == OverviewAnimationType.NEW_TAB_OPENED) { - animator = createNewTabOpenedAnimator(tabs, container, list, focusIndex); - } - - return animator; - } - - private Animator createNewTabOpenedAnimator( - StackTab[] tabs, ViewGroup container, TabList list, int focusIndex) { - Tab tab = list.getTabAt(focusIndex); - if (tab == null || !tab.isNativePage()) return null; - - View view = tab.getView(); - if (view == null) return null; - - // Set up the view hierarchy - if (view.getParent() != null) ((ViewGroup) view.getParent()).removeView(view); - ViewGroup bgView = new FrameLayout(view.getContext()); - bgView.setBackgroundColor(ThemeUtils.getBackgroundColor(tab)); - bgView.addView(view); - container.addView( - bgView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - // Update any compositor state that needs to change - if (tabs != null && focusIndex >= 0 && focusIndex < tabs.length) { - tabs[focusIndex].setAlpha(0.f); - } - - // Build the view animations - PropertyValuesHolder viewAlpha = PropertyValuesHolder.ofFloat(View.ALPHA, 0.f, 1.f); - ObjectAnimator viewAlphaAnimator = ObjectAnimator.ofPropertyValuesHolder(view, viewAlpha); - viewAlphaAnimator.setDuration(TAB_OPENED_VIEW_ANIMATION_DURATION); - viewAlphaAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR); - - PropertyValuesHolder yTranslation = - PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, mTranslationYStart, 0.f); - ObjectAnimator viewYTranslationAnimator = - ObjectAnimator.ofPropertyValuesHolder(view, yTranslation); - viewYTranslationAnimator.setDuration(TAB_OPENED_VIEW_ANIMATION_DURATION); - viewYTranslationAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR); - - PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat(View.ALPHA, 0.f, 1.f); - ObjectAnimator bgAlphaAnimator = ObjectAnimator.ofPropertyValuesHolder(bgView, bgAlpha); - bgAlphaAnimator.setDuration(TAB_OPENED_BG_ANIMATION_DURATION); - bgAlphaAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR); - - AnimatorSet set = new AnimatorSet(); - set.playTogether(viewAlphaAnimator, viewYTranslationAnimator, bgAlphaAnimator); - - return set; - } -} diff --git a/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java b/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java index bb236156a28f..bc4d0db63532 100644 --- a/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java +++ b/android/java/org/chromium/chrome/browser/settings/AppearancePreferences.java @@ -34,7 +34,6 @@ public class AppearancePreferences extends BravePreferenceFragment implements Preference.OnPreferenceChangeListener, BraveRewardsObserver { public static final String PREF_HIDE_BRAVE_REWARDS_ICON = "hide_brave_rewards_icon"; public static final String PREF_BRAVE_NIGHT_MODE_ENABLED = "brave_night_mode_enabled_key"; - public static final String PREF_BRAVE_ENABLE_TAB_GROUPS = "brave_enable_tab_groups"; public static final String PREF_BRAVE_DISABLE_SHARING_HUB = "brave_disable_sharing_hub"; private BraveRewardsNativeWorker mBraveRewardsNativeWorker; @@ -48,7 +47,6 @@ public void onCreate(Bundle savedInstanceState) { ContextUtils.getApplicationContext()); if (isTablet) { removePreferenceIfPresent(BravePreferenceKeys.BRAVE_BOTTOM_TOOLBAR_ENABLED_KEY); - removePreferenceIfPresent(PREF_BRAVE_ENABLE_TAB_GROUPS); } if (!NightModeUtils.isNightModeSupported()) { @@ -102,15 +100,6 @@ public void onActivityCreated(Bundle savedInstanceState) { } } - Preference enableTabGroups = findPreference(PREF_BRAVE_ENABLE_TAB_GROUPS); - if (enableTabGroups != null) { - enableTabGroups.setOnPreferenceChangeListener(this); - if (enableTabGroups instanceof ChromeSwitchPreference) { - ((ChromeSwitchPreference) enableTabGroups) - .setChecked(TabUiFeatureUtilities.isTabGroupsAndroidEnabled(getActivity())); - } - } - Preference disableSharingHub = findPreference(PREF_BRAVE_DISABLE_SHARING_HUB); if (disableSharingHub != null) { disableSharingHub.setOnPreferenceChangeListener(this); @@ -160,16 +149,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { BraveFeatureList.enableFeature( BraveFeatureList.ENABLE_FORCE_DARK, (boolean) newValue, true); BraveRelaunchUtils.askForRelaunch(getActivity()); - } else if (PREF_BRAVE_ENABLE_TAB_GROUPS.equals(key)) { - if ((boolean) newValue) { - // Revert these features to default values, we will not rely on them anymore. - BraveFeatureList.enableFeature(BraveFeatureList.ENABLE_TAB_GROUPS, false, true); - BraveFeatureList.enableFeature(BraveFeatureList.ENABLE_TAB_GRID, false, true); - ChromeCachedFlags.getInstance().cacheNativeFlags(); - } - SharedPreferencesManager.getInstance().writeBoolean( - BravePreferenceKeys.BRAVE_TAB_GROUPS_ENABLED, (boolean) newValue); - BraveRelaunchUtils.askForRelaunch(getActivity()); } else if (PREF_BRAVE_DISABLE_SHARING_HUB.equals(key)) { SharedPreferencesManager.getInstance().writeBoolean( BravePreferenceKeys.BRAVE_DISABLE_SHARING_HUB, (boolean) newValue); diff --git a/android/java/res/values-hdpi/brave_dimens.xml b/android/java/res/values-hdpi/brave_dimens.xml deleted file mode 100644 index eafa125926c5..000000000000 --- a/android/java/res/values-hdpi/brave_dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - 5.333dp - 49.333dp - diff --git a/android/java/res/values-xhdpi/brave_dimens.xml b/android/java/res/values-xhdpi/brave_dimens.xml deleted file mode 100644 index dfed3bf845a2..000000000000 --- a/android/java/res/values-xhdpi/brave_dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - 5.5dp - 49.5dp - diff --git a/android/java/res/values-xxhdpi/brave_dimens.xml b/android/java/res/values-xxhdpi/brave_dimens.xml deleted file mode 100644 index eafa125926c5..000000000000 --- a/android/java/res/values-xxhdpi/brave_dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - 5.333dp - 49.333dp - diff --git a/android/java/res/values/brave_dimens.xml b/android/java/res/values/brave_dimens.xml index 5bcd5db0bf2d..83b174e8bb76 100644 --- a/android/java/res/values/brave_dimens.xml +++ b/android/java/res/values/brave_dimens.xml @@ -53,26 +53,5 @@ 30dp 15dp - - 4dp - - 5dp - - 5dp - - 120dp - - 75dp - - 400dp - - 10dp - - 6dp - 50dp - 3dp - 2dp - -20dp - 0dp diff --git a/android/java/res/values/brave_values.xml b/android/java/res/values/brave_values.xml deleted file mode 100644 index 5cdf9614cacf..000000000000 --- a/android/java/res/values/brave_values.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - 15 - - diff --git a/android/java/res/xml/appearance_preferences.xml b/android/java/res/xml/appearance_preferences.xml index 3c399b1ca7de..dba1d7cf9dc5 100644 --- a/android/java/res/xml/appearance_preferences.xml +++ b/android/java/res/xml/appearance_preferences.xml @@ -23,12 +23,6 @@ android:summaryOn="@string/text_on" android:summaryOff="@string/text_off" /> - - Disable non-proxied UDP - - Enable Tab Groups - Verifying wallet allows you to manage your funds easily, adding & withdrawing diff --git a/build/android/bytecode/BUILD.gn b/build/android/bytecode/BUILD.gn index f45141529bbf..63ec469d0f66 100644 --- a/build/android/bytecode/BUILD.gn +++ b/build/android/bytecode/BUILD.gn @@ -30,7 +30,6 @@ java_binary("java_bytecode_rewriter") { "//brave/build/android/bytecode/java/org/brave/bytecode/BraveIncognitoToggleTabLayoutClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveIntentHandlerClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveLaunchIntentDispatcherClassAdapter.java", - "//brave/build/android/bytecode/java/org/brave/bytecode/BraveLayoutManagerChromeClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveMainPreferenceBaseClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveManageSyncSettingsClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveMenuButtonCoordinatorClassAdapter.java", @@ -54,7 +53,6 @@ java_binary("java_bytecode_rewriter") { "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabGroupUiCoordinatorClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabSwitcherModeTTCoordinatorClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabSwitcherModeTopToolbarClassAdapter.java", - "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabUiFeatureUtilitiesClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabUiThemeProviderClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveTabbedActivityClassAdapter.java", "//brave/build/android/bytecode/java/org/brave/bytecode/BraveThemeUtilsClassAdapter.java", diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java index 884de0136171..ac0d3c5faafd 100644 --- a/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java +++ b/build/android/bytecode/java/org/brave/bytecode/BraveClassAdapter.java @@ -29,7 +29,6 @@ public static ClassVisitor createAdapter(ClassVisitor chain) { chain = new BraveIncognitoToggleTabLayoutClassAdapter(chain); chain = new BraveIntentHandlerClassAdapter(chain); chain = new BraveLaunchIntentDispatcherClassAdapter(chain); - chain = new BraveLayoutManagerChromeClassAdapter(chain); chain = new BraveMainPreferenceBaseClassAdapter(chain); chain = new BraveManageSyncSettingsClassAdapter(chain); chain = new BraveMenuButtonCoordinatorClassAdapter(chain); @@ -53,7 +52,6 @@ public static ClassVisitor createAdapter(ClassVisitor chain) { chain = new BraveTabGroupUiCoordinatorClassAdapter(chain); chain = new BraveTabSwitcherModeTTCoordinatorClassAdapter(chain); chain = new BraveTabSwitcherModeTopToolbarClassAdapter(chain); - chain = new BraveTabUiFeatureUtilitiesClassAdapter(chain); chain = new BraveTabUiThemeProviderClassAdapter(chain); chain = new BraveTabbedActivityClassAdapter(chain); chain = new BraveThemeUtilsClassAdapter(chain); diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveLayoutManagerChromeClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveLayoutManagerChromeClassAdapter.java deleted file mode 100644 index 04655bf43059..000000000000 --- a/build/android/bytecode/java/org/brave/bytecode/BraveLayoutManagerChromeClassAdapter.java +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.brave.bytecode; - -import org.objectweb.asm.ClassVisitor; - -public class BraveLayoutManagerChromeClassAdapter extends BraveClassVisitor { - static String sLayoutManagerChromePhoneClassName = - "org/chromium/chrome/browser/compositor/layouts/LayoutManagerChromePhone"; - - static String sBraveLayoutManagerChromeClassName = - "org/chromium/chrome/browser/compositor/layouts/BraveLayoutManagerChrome"; - - public BraveLayoutManagerChromeClassAdapter(ClassVisitor visitor) { - super(visitor); - changeSuperName(sLayoutManagerChromePhoneClassName, sBraveLayoutManagerChromeClassName); - } -} diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveTabUiFeatureUtilitiesClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveTabUiFeatureUtilitiesClassAdapter.java deleted file mode 100644 index 404895cedac6..000000000000 --- a/build/android/bytecode/java/org/brave/bytecode/BraveTabUiFeatureUtilitiesClassAdapter.java +++ /dev/null @@ -1,25 +0,0 @@ -/* Copyright (c) 2021 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.brave.bytecode; - -import org.objectweb.asm.ClassVisitor; - -public class BraveTabUiFeatureUtilitiesClassAdapter extends BraveClassVisitor { - static String sTabUiFeatureUtilitiesClassName = - "org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities"; - static String sBraveTabUiFeatureUtilitiesClassName = - "org/chromium/chrome/browser/tasks/tab_management/BraveTabUiFeatureUtilities"; - - public BraveTabUiFeatureUtilitiesClassAdapter(ClassVisitor visitor) { - super(visitor); - - changeMethodOwner(sTabUiFeatureUtilitiesClassName, "isGridTabSwitcherEnabled", - sBraveTabUiFeatureUtilitiesClassName); - - changeMethodOwner(sTabUiFeatureUtilitiesClassName, "isTabGroupsAndroidEnabled", - sBraveTabUiFeatureUtilitiesClassName); - } -} diff --git a/patches/chrome-android-java-src-org-chromium-chrome-browser-ChromeTabbedActivity.java.patch b/patches/chrome-android-java-src-org-chromium-chrome-browser-ChromeTabbedActivity.java.patch deleted file mode 100644 index 1645931e428e..000000000000 --- a/patches/chrome-android-java-src-org-chromium-chrome-browser-ChromeTabbedActivity.java.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java -index b6fcd80178403d0f68c45bac90067b9ab163f90a..d56e79410b7ee91548bdb24b4a462f9bee1c8e9a 100644 ---- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java -+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java -@@ -2385,6 +2385,7 @@ public class ChromeTabbedActivity extends ChromeActivity