chromium/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser;

import static org.chromium.chrome.browser.ui.IncognitoRestoreAppLaunchDrawBlocker.IS_INCOGNITO_SELECTED;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Browser;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleRegistry;

import org.chromium.base.ApplicationStatus;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
import org.chromium.base.CallbackController;
import org.chromium.base.CommandLine;
import org.chromium.base.IntentUtils;
import org.chromium.base.Log;
import org.chromium.base.MemoryPressureListener;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.jank_tracker.JankTracker;
import org.chromium.base.jank_tracker.JankTrackerImpl;
import org.chromium.base.jank_tracker.PlaceholderJankTracker;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.LazyOneshotSupplier;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneShotCallback;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.supplier.UnownedUserDataSupplier;
import org.chromium.base.supplier.UnwrapObservableSupplier;
import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.BackgroundOnlyAsyncTask;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.build.annotations.UsedByReflection;
import org.chromium.cc.input.BrowserControlsState;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler.TabOpenType;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
import org.chromium.chrome.browser.app.metrics.TabbedActivityLaunchCauseMetrics;
import org.chromium.chrome.browser.app.tabmodel.ArchivedTabModelOrchestrator;
import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
import org.chromium.chrome.browser.app.tabmodel.ChromeNextTabPolicySupplier;
import org.chromium.chrome.browser.app.tabmodel.TabModelOrchestrator;
import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
import org.chromium.chrome.browser.app.tabmodel.TabbedModeTabModelOrchestrator;
import org.chromium.chrome.browser.auxiliary_search.AuxiliarySearchController;
import org.chromium.chrome.browser.auxiliary_search.AuxiliarySearchControllerFactory;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.back_press.MinimizeAppAndCloseTabBackPressHandler;
import org.chromium.chrome.browser.back_press.MinimizeAppAndCloseTabBackPressHandler.MinimizeAppAndCloseTabType;
import org.chromium.chrome.browser.base.ColdStartTracker;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromeTablet;
import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager.TabModelStartupInfo;
import org.chromium.chrome.browser.cookies.CookiesFetcher;
import org.chromium.chrome.browser.crypto.CipherFactory;
import org.chromium.chrome.browser.data_sharing.DataSharingNotificationManager;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.dom_distiller.ReaderModeManager;
import org.chromium.chrome.browser.download.DownloadNotificationService;
import org.chromium.chrome.browser.download.DownloadOpenSource;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.dragdrop.ChromeDragAndDropBrowserDelegate;
import org.chromium.chrome.browser.educational_tip.EducationalTipModuleBuilder;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.feed.FeedSurfaceTracker;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.fonts.FontPreloader;
import org.chromium.chrome.browser.gesturenav.NavigationSheet;
import org.chromium.chrome.browser.history.HistoryManager;
import org.chromium.chrome.browser.history.HistoryManagerUtils;
import org.chromium.chrome.browser.homepage.HomepageManager;
import org.chromium.chrome.browser.hub.DefaultPaneOrderController;
import org.chromium.chrome.browser.hub.HubLayoutDependencyHolder;
import org.chromium.chrome.browser.hub.HubManager;
import org.chromium.chrome.browser.hub.HubProvider;
import org.chromium.chrome.browser.hub.Pane;
import org.chromium.chrome.browser.hub.PaneId;
import org.chromium.chrome.browser.incognito.IncognitoNotificationManager;
import org.chromium.chrome.browser.incognito.IncognitoNotificationPresenceController;
import org.chromium.chrome.browser.incognito.IncognitoProfileDestroyer;
import org.chromium.chrome.browser.incognito.IncognitoStartup;
import org.chromium.chrome.browser.incognito.IncognitoTabLauncher;
import org.chromium.chrome.browser.incognito.IncognitoTabbedSnapshotController;
import org.chromium.chrome.browser.incognito.IncognitoUtils;
import org.chromium.chrome.browser.init.ActivityProfileProvider;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher.ActivityState;
import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.magic_stack.HomeModulesConfigManager;
import org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils;
import org.chromium.chrome.browser.magic_stack.ModuleDelegate.ModuleType;
import org.chromium.chrome.browser.magic_stack.ModuleRegistry;
import org.chromium.chrome.browser.metrics.AndroidSessionDurationsServiceState;
import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
import org.chromium.chrome.browser.modaldialog.ChromeTabModalPresenter;
import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils.InstanceAllocationType;
import org.chromium.chrome.browser.native_page.NativePageAssassin;
import org.chromium.chrome.browser.navigation_predictor.NavigationPredictorBridge;
import org.chromium.chrome.browser.new_tab_url.DseNewTabUrlManager;
import org.chromium.chrome.browser.ntp.NewTabPageLaunchOrigin;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.ntp.NewTabPageUtils;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
import org.chromium.chrome.browser.paint_preview.StartupPaintPreviewHelper;
import org.chromium.chrome.browser.paint_preview.StartupPaintPreviewHelperSupplier;
import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.price_change.PriceChangeModuleBuilder;
import org.chromium.chrome.browser.profiles.OTRProfileID;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.profiles.ProfileProvider;
import org.chromium.chrome.browser.quick_delete.QuickDeleteController;
import org.chromium.chrome.browser.quick_delete.QuickDeleteDelegateImpl;
import org.chromium.chrome.browser.quick_delete.QuickDeleteMetricsDelegate;
import org.chromium.chrome.browser.read_later.ReadingListBackPressHandler;
import org.chromium.chrome.browser.recent_tabs.CrossDevicePaneFactory;
import org.chromium.chrome.browser.reengagement.ReengagementNotificationController;
import org.chromium.chrome.browser.safety_hub.SafetyHubMagicStackBuilder;
import org.chromium.chrome.browser.search_engines.SearchEngineChoiceNotification;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfAndroidBridge;
import org.chromium.chrome.browser.single_tab.SingleTabModuleBuilder;
import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
import org.chromium.chrome.browser.survey.ChromeSurveyController;
import org.chromium.chrome.browser.sync.ui.SyncErrorMessage;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.RedirectHandlerTabHelper;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabAssociatedApp;
import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabObscuringHandler;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab.tab_restore.HistoricalTabModelObserver;
import org.chromium.chrome.browser.tab_resumption.TabResumptionModuleBuilder;
import org.chromium.chrome.browser.tab_ui.TabSwitcher;
import org.chromium.chrome.browser.tab_ui.TabSwitcherUtils;
import org.chromium.chrome.browser.tabbed_mode.TabbedAppMenuPropertiesDelegate;
import org.chromium.chrome.browser.tabbed_mode.TabbedRootUiCoordinator;
import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
import org.chromium.chrome.browser.tabmodel.IncognitoTabHost;
import org.chromium.chrome.browser.tabmodel.IncognitoTabHostRegistry;
import org.chromium.chrome.browser.tabmodel.IncognitoTabHostUtils;
import org.chromium.chrome.browser.tabmodel.MismatchedIndicesHandler;
import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorBase;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.chrome.browser.tasks.HomeSurfaceTracker;
import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
import org.chromium.chrome.browser.tasks.TasksUma;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
import org.chromium.chrome.browser.tasks.tab_management.CloseAllTabsDialog;
import org.chromium.chrome.browser.tasks.tab_management.CloseAllTabsHelper;
import org.chromium.chrome.browser.tasks.tab_management.TabGroupUi;
import org.chromium.chrome.browser.tasks.tab_management.TabGroupVisualDataManager;
import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegateProvider;
import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
import org.chromium.chrome.browser.toolbar.ToolbarIntentMetadata;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer;
import org.chromium.chrome.browser.ui.AppLaunchDrawBlocker;
import org.chromium.chrome.browser.ui.IncognitoRestoreAppLaunchDrawBlockerFactory;
import org.chromium.chrome.browser.ui.RootUiCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.native_page.NativePage;
import org.chromium.chrome.browser.undo_tab_close_snackbar.UndoBarController;
import org.chromium.chrome.browser.usage_stats.UsageStatsService;
import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
import org.chromium.components.browser_ui.util.FirstDrawDetector;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.components.external_intents.ExternalNavigationHandler;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.messages.MessageDispatcherProvider;
import org.chromium.components.profile_metrics.BrowserProfileType;
import org.chromium.components.webapps.ShortcutSource;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.RenderFrameHost;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.dragdrop.DragAndDropDelegate;
import org.chromium.ui.dragdrop.DragAndDropDelegateImpl;
import org.chromium.ui.widget.Toast;
import org.chromium.url.GURL;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.DoubleConsumer;

/**
 * This is the main activity for ChromeMobile when not running in document mode. All the tabs are
 * accessible via a chrome specific tab switching UI.
 */
public class ChromeTabbedActivity extends ChromeActivity<ChromeActivityComponent>
        implements MismatchedIndicesHandler {
    private static final String TAG = "ChromeTabbedActivity";

    protected static final String WINDOW_INDEX = "window_index";

    private static final int INVALID_WINDOW_ID = TabWindowManager.INVALID_WINDOW_INDEX;

    // How long to delay closing the current tab when our app is minimized.  Have to delay this
    // so that we don't show the contents of the next tab while minimizing.
    private static final long CLOSE_TAB_ON_MINIMIZE_DELAY_MS = 500;

    // Maximum delay for initial tab creation. This is for homepage and NTP, not previous tabs
    // restore. This is needed because we do not know when reading PartnerBrowserCustomizations
    // provider will be finished.
    private static final int INITIAL_TAB_CREATION_TIMEOUT_MS = 500;

    /**
     * Sending an intent with this action to Chrome will cause it to close all tabs
     * (iff the --enable-test-intents command line flag is set). If a URL is supplied in the
     * intent data, this will be loaded and unaffected by the close all action.
     */
    private static final String ACTION_CLOSE_TABS =
            "com.google.android.apps.chrome.ACTION_CLOSE_TABS";

    // Name of the ChromeTabbedActivity alias that handles MAIN intents.
    public static final String MAIN_LAUNCHER_ACTIVITY_NAME = "com.google.android.apps.chrome.Main";

    public static final Set<String> TABBED_MODE_COMPONENT_NAMES =
            Set.of(
                    ChromeTabbedActivity.class.getName(),
                    ChromeTabbedActivity2.class.getName(),
                    MAIN_LAUNCHER_ACTIVITY_NAME);

    static final String HISTOGRAM_EXPLICIT_VIEW_INTENT_FINISHED_NEW_ACTIVITY =
            "Android.ExplicitViewIntentFinishedNewTabbedActivity";

    private static final String TAG_MULTI_INSTANCE = "MultiInstance";

    static final String HISTOGRAM_MISMATCHED_INDICES_ACTIVITY_CREATION_TIME_DELTA =
            "Android.MultiWindowMode.MismatchedIndices.ActivityCreationTimeDelta";

    public static final String HISTOGRAM_DRAGGED_TAB_OPENED_NEW_WINDOW =
            "Android.MultiWindowMode.DraggedTabOpenedNewWindow";

    /**
     * Identifies a histogram to use in {@link #maybeDispatchExplicitMainViewIntent(Intent, int)}.
     */
    @IntDef({DispatchedBy.ON_CREATE, DispatchedBy.ON_NEW_INTENT})
    @Retention(RetentionPolicy.SOURCE)
    private @interface DispatchedBy {
        int ON_CREATE = 1;
        int ON_NEW_INTENT = 2;
    }

    // Time histogram used to track time to inflate tab switcher views.
    private static final String TAB_SWITCHER_CREATION_TIME = "Android.TabSwitcher.CreationTime";

    private final MainIntentBehaviorMetrics mMainIntentMetrics;
    private @Nullable MultiInstanceManager mMultiInstanceManager;

    private UndoBarController mUndoBarPopupController;

    private LayoutManagerChrome mLayoutManager;

    private ViewGroup mContentContainer;

    private ToolbarControlContainer mControlContainer;

    private TabbedModeTabModelOrchestrator mTabModelOrchestrator;
    private TabModelSelectorBase mTabModelSelector;
    private TabModelSelectorObserver mTabModelSelectorObserver;
    private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
    private TabModelSelectorTabModelObserver mTabModelObserver;
    private HistoricalTabModelObserver mHistoricalTabModelObserver;
    private UndoRefocusHelper mUndoRefocusHelper;

    private BrowserControlsVisibilityDelegate mVrBrowserControlsVisibilityDelegate;

    private boolean mUIWithNativeInitialized;

    private LocaleManager mLocaleManager;

    private Runnable mShowHistoryRunnable;

    private CompositorViewHolder mCompositorViewHolder;

    /** Keeps track of whether or not a specific tab was created based on the startup intent. */
    private boolean mCreatedTabOnStartup;

    // Whether or not the initial tab is being created.
    private boolean mPendingInitialTabCreation;

    // Whether {@link setInitialOverviewState()} has been called within the current onStart/onStop
    // session.
    private boolean mHasDeterminedOverviewStateForCurrentSession;

    /** Keeps track of the pref for the last time since this activity was stopped. */
    private ChromeInactivityTracker mInactivityTracker;

    /** The controller for the auxiliary search. */
    private @Nullable AuxiliarySearchController mAuxiliarySearchController;

    // This is the cached value of IntentHandler#shouldIgnoreIntent and shouldn't be read directly.
    // Use #shouldIgnoreIntent instead.
    private Boolean mShouldIgnoreIntent;

    // Listens to FrameMetrics and records janks.
    private JankTracker mJankTracker;

    // Supplier for a dependency to inform about the type of intent used to launch Chrome.
    private OneshotSupplierImpl<ToolbarIntentMetadata> mIntentMetadataOneshotSupplier =
            new OneshotSupplierImpl<>();

    // Whether the activity is staring from a resumption. False if the activity is starting from
    // onCreate(), a cold startup.
    private boolean mFromResumption;

    private NextTabPolicySupplier mNextTabPolicySupplier;

    private final UnownedUserDataSupplier<StartupPaintPreviewHelper>
            mStartupPaintPreviewHelperSupplier = new StartupPaintPreviewHelperSupplier();

    private final OneshotSupplierImpl<LayoutStateProvider> mLayoutStateProviderSupplier =
            new OneshotSupplierImpl<>();
    private final OneshotSupplierImpl<TabSwitcher> mTabSwitcherSupplier =
            new OneshotSupplierImpl<>();
    private final OneshotSupplierImpl<TabSwitcher> mIncognitoTabSwitcherSupplier =
            new OneshotSupplierImpl<>();
    private HubProvider mHubProvider;
    private OneshotSupplierImpl<HubManager> mHubManagerSupplier = new OneshotSupplierImpl<>();
    private Runnable mCleanUpHubOverviewColorObserver;
    private ObservableSupplierImpl<TabModelStartupInfo> mTabModelStartupInfoSupplier;

    private CallbackController mCallbackController = new CallbackController();
    private TabbedModeTabDelegateFactory mTabDelegateFactory;

    private final AppLaunchDrawBlocker mAppLaunchDrawBlocker;

    private ReadingListBackPressHandler mReadingListBackPressHandler;
    private MinimizeAppAndCloseTabBackPressHandler mMinimizeAppAndCloseTabBackPressHandler;

    private HomeSurfaceTracker mHomeSurfaceTracker;

    // ID assigned to each ChromeTabbedActivity instance in Android S+ where multi-instance feature
    // is supported. This can be explicitly set in the incoming Intent or internally assigned.
    private int mWindowId;

    private @InstanceAllocationType int mInstanceAllocationType;

    // The URL of the last active Tab read from the Tab metadata file during cold startup.
    private String mLastActiveTabUrl;

    private DseNewTabUrlManager mDseNewTabUrlManager;

    // Time at which an intent was received and handled.
    private long mIntentHandlingTimeMs;

    // Delegate to handle drag and drop features for tablets.
    private DragAndDropDelegate mDragDropDelegate;

    private OneshotSupplierImpl<ModuleRegistry> mModuleRegistrySupplier =
            new OneshotSupplierImpl<>();

    private final IncognitoTabHost mIncognitoTabHost =
            new IncognitoTabHost() {
                @Override
                public boolean hasIncognitoTabs() {
                    return getTabModelSelector().getModel(true).getCount() > 0;
                }

                @Override
                public void closeAllIncognitoTabs() {
                    if (isActivityFinishingOrDestroyed()) return;

                    // If the tabbed activity has not yet initialized, then finish the activity to
                    // avoid timing issues with clearing the incognito tab state in the
                    // background.
                    if (!areTabModelsInitialized() || !didFinishNativeInitialization()) {
                        finish();
                        return;
                    }

                    getTabModelSelector()
                            .getModel(true)
                            .closeTabs(TabClosureParams.closeAllTabs().build());
                }

                @Override
                public boolean isActiveModel() {
                    return getTabModelSelector().getModel(true).isActiveModel();
                }
            };

    private final OnClickListener mNewTabButtonClickListener =
            view -> {
                getTabModelSelector().getModel(false).commitAllTabClosures();
                // This assumes that the keyboard can not be seen at the same time as the
                // new tab button on the toolbar.
                int tabLaunchType =
                        (getLayoutManager().getActiveLayoutType() == LayoutType.TAB_SWITCHER)
                                ? TabLaunchType.FROM_TAB_SWITCHER_UI
                                : TabLaunchType.FROM_CHROME_UI;
                getCurrentTabCreator().launchNtp(tabLaunchType);
                mLocaleManager.showSearchEnginePromoIfNeeded(ChromeTabbedActivity.this, null);
                if (getTabModelSelector().isIncognitoSelected()) {
                    RecordUserAction.record("MobileToolbarStackViewNewIncognitoTab");
                } else {
                    RecordUserAction.record("MobileToolbarStackViewNewTab");
                }
                RecordUserAction.record("MobileTopToolbarNewTabButton");

                RecordUserAction.record("MobileNewTabOpened");
            };

    // Manager for tab group visual data lifecycle updates.
    private TabGroupVisualDataManager mTabGroupVisualDataManager;

    /**
     * This class is used to warm up the chrome split ClassLoader. See SplitChromeApplication for
     * more info
     */
    @UsedByReflection("SplitChromeApplication.java")
    public static class Preload extends ChromeTabbedActivity {
        private LifecycleRegistry mLifecycleRegistry;

        @UsedByReflection("SplitChromeApplication.java")
        public Preload() {}

        @Override
        public Lifecycle getLifecycle() {
            if (mLifecycleRegistry == null) {
                // LifecycleRegistry normally enforces it is called on the main thread, but this
                // class will be preloaded in a background thread. The only method that gets called
                // in the activity constructor is addObserver(), so just override that.
                mLifecycleRegistry =
                        new LifecycleRegistry(this) {
                            @Override
                            public void addObserver(LifecycleObserver observer) {}
                        };
            }
            return mLifecycleRegistry;
        }
    }

    /** Constructs a ChromeTabbedActivity. */
    public ChromeTabbedActivity() {
        mIntentHandlingTimeMs = SystemClock.uptimeMillis();
        mMainIntentMetrics = new MainIntentBehaviorMetrics();

        // AppLaunchDrawBlocker may block drawing the Activity content until the initial tab is
        // available.
        mAppLaunchDrawBlocker =
                new AppLaunchDrawBlocker(
                        getLifecycleDispatcher(),
                        () -> findViewById(android.R.id.content),
                        this::getIntent,
                        this::shouldIgnoreIntent,
                        this::isTablet,
                        mTabModelProfileSupplier,
                        new IncognitoRestoreAppLaunchDrawBlockerFactory(
                                this::getSavedInstanceState, getTabModelSelectorSupplier()));
    }

    @Override
    protected void onPreCreate() {
        super.onPreCreate();
        mMultiInstanceManager =
                MultiInstanceManager.create(
                        this,
                        getTabModelOrchestratorSupplier(),
                        getMultiWindowModeStateDispatcher(),
                        getLifecycleDispatcher(),
                        getModalDialogManagerSupplier(),
                        this,
                        () ->
                                mRootUiCoordinator != null
                                        ? mRootUiCoordinator.getDesktopWindowStateProvider()
                                        : null);
        mBackPressManager.setFallbackOnBackPressed(
                () -> {
                    if (BackPressManager.correctTabNavigationOnFallback()) {
                        if (getToolbarManager() != null && getToolbarManager().back()) {
                            return;
                        }
                    }
                    minimizeAppAndCloseTabOnBackPress(getActivityTab());
                });
    }

    @Override
    protected @LaunchIntentDispatcher.Action int maybeDispatchLaunchIntent(
            Intent intent, Bundle savedInstanceState) {
        // Detect if incoming intent is a result of Chrome recreating itself. For now, restrict this
        // path to reparenting to ensure the launching logic isn't disrupted.
        // TODO(crbug.com/40681858): Unlock this codepath for all incoming intents once it's
        // confirmed working and stable.
        if (savedInstanceState != null
                && AsyncTabParamsManagerSingleton.getInstance().hasParamsWithTabToReparent()) {
            return LaunchIntentDispatcher.Action.CONTINUE;
        }

        if (getClass().equals(ChromeTabbedActivity.class)
                && Intent.ACTION_MAIN.equals(intent.getAction())) {
            // Call dispatchToTabbedActivity() for MAIN intents to activate proper multi-window
            // TabbedActivity (i.e. if CTA2 is currently running and Chrome is started, CTA2
            // should be brought to front). Don't call dispatchToTabbedActivity() for non-MAIN
            // intents to avoid breaking cases where CTA is started explicitly (e.g. to handle
            // 'Move to other window' command from CTA2).
            return LaunchIntentDispatcher.dispatchToTabbedActivity(this, intent);
        }
        @LaunchIntentDispatcher.Action
        int action = maybeDispatchExplicitMainViewIntent(intent, DispatchedBy.ON_CREATE);
        if (action != LaunchIntentDispatcher.Action.CONTINUE) {
            Log.i(TAG_MULTI_INSTANCE, "Dispatched explicit .Main (CTA) VIEW intent to CCT.");
            return action;
        }
        return super.maybeDispatchLaunchIntent(intent, savedInstanceState);
    }

    // We know of at least one app that explicitly specifies .Main activity in custom tab
    // intents. The app shouldn't be doing that, but until it's updated, we need to support
    // such use case.
    //
    // This method attempts to treat VIEW intents explicitly sent to .Main as custom tab
    // intents, and dispatch them accordingly. If the intent was not dispatched, the method
    // returns Action.CONTINUE.
    //
    // The method also updates the supplied boolean histogram with the dispatching result,
    // but only if the intent is a VIEW intent sent explicitly to .Main activity.
    private @LaunchIntentDispatcher.Action int maybeDispatchExplicitMainViewIntent(
            Intent intent, @DispatchedBy int dispatchedBy) {
        // The first check ensures that this is .Main activity alias (we can't check exactly, but
        // this gets us sufficiently close).
        if (getClass().equals(ChromeTabbedActivity.class)
                && Intent.ACTION_VIEW.equals(intent.getAction())
                && intent.getComponent() != null
                && MAIN_LAUNCHER_ACTIVITY_NAME.equals(intent.getComponent().getClassName())) {
            @LaunchIntentDispatcher.Action
            int action = LaunchIntentDispatcher.dispatchToCustomTabActivity(this, intent);
            switch (dispatchedBy) {
                case DispatchedBy.ON_CREATE:
                case DispatchedBy.ON_NEW_INTENT:
                    break;
                default:
                    assert false : "Unknown dispatchedBy value " + dispatchedBy;
            }
            if (action == LaunchIntentDispatcher.Action.CONTINUE) {
                // Crash if intent came from us, but only in debug builds and only if we weren't
                // explicitly told not to. Hopefully we'll get enough reports to find where
                // these intents come from.
                if (IntentHandler.isExternalIntentSourceChrome(intent)
                        && BuildInfo.isDebugApp()
                        && !CommandLine.getInstance()
                                .hasSwitch(ChromeSwitches.DONT_CRASH_ON_VIEW_MAIN_INTENTS)) {
                    String intentInfo = intent.toString();
                    Bundle extras = intent.getExtras();
                    if (extras != null) {
                        intentInfo +=
                                ", extras.keySet = [" + TextUtils.join(", ", extras.keySet()) + "]";
                    }
                    String message =
                            String.format(
                                    """
                                    VIEW intent sent to .Main activity alias was not dispatched. \
                                    PLEASE report the following info to crbug.com/789732: \
                                    "%s". Use --%s flag to disable this check.""",
                                    intentInfo, ChromeSwitches.DONT_CRASH_ON_VIEW_MAIN_INTENTS);
                    throw new IllegalStateException(message);
                }
            }
            return action;
        }
        return LaunchIntentDispatcher.Action.CONTINUE;
    }

    @Override
    public void initializeCompositor() {
        try {
            TraceEvent.begin("ChromeTabbedActivity.initializeCompositor");
            super.initializeCompositor();

            // LocaleManager can only function after the native library is loaded.
            mLocaleManager = LocaleManager.getInstance();
            mLocaleManager.showSearchEnginePromoIfNeeded(this, null);

            mTabModelOrchestrator.onNativeLibraryReady(getTabContentManager());

            TabModelUtils.runOnTabStateInitialized(
                    mTabModelSelector,
                    mCallbackController.makeCancelable(
                            (tabModelSelector) -> {
                                assert tabModelSelector != null;
                                mTabGroupVisualDataManager =
                                        new TabGroupVisualDataManager(tabModelSelector);
                            }));

            Profile profile = mTabModelSelector.getCurrentModel().getProfile();
            // For saving non-incognito tab closures for Recent Tabs.
            mHistoricalTabModelObserver =
                    new HistoricalTabModelObserver(
                            mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(false));
            mHistoricalTabModelObserver.addSecodaryTabModelSupplier(
                    ArchivedTabModelOrchestrator.getForProfile(profile)::getTabModel);

            // Defer creation of this helper so it triggers after TabModelFilter observers.
            mUndoRefocusHelper =
                    new UndoRefocusHelper(
                            mTabModelSelector, getLayoutManagerSupplier(), isTablet());

            mTabModelObserver =
                    new TabModelSelectorTabModelObserver(mTabModelSelector) {
                        @Override
                        public void onFinishingTabClosure(Tab tab) {
                            closeIfNoTabsAndHomepageEnabled(false);
                        }

                        @Override
                        public void tabPendingClosure(Tab tab) {
                            closeIfNoTabsAndHomepageEnabled(true);
                        }

                        @Override
                        public void multipleTabsPendingClosure(List<Tab> tabs, boolean isAllTabs) {
                            closeIfNoTabsAndHomepageEnabled(true);
                        }

                        @Override
                        public void tabRemoved(Tab tab) {
                            closeIfNoTabsAndHomepageEnabled(false);
                        }

                        private void closeIfNoTabsAndHomepageEnabled(boolean isPendingClosure) {
                            if (getTabModelSelector().getTotalTabCount() == 0) {
                                // If the last tab is closed, and homepage is enabled, then exit
                                // Chrome.
                                if (HomepageManager.getInstance().shouldCloseAppWithZeroTabs()) {
                                    finish();
                                } else if (isPendingClosure) {
                                    NewTabPageUma.recordNtpImpression(
                                            NewTabPageUma.NTP_IMPRESSION_POTENTIAL_NO_TAB);
                                }
                            }
                        }

                        @Override
                        public void didAddTab(
                                Tab tab,
                                @TabLaunchType int type,
                                @TabCreationState int creationState,
                                boolean markedForSelection) {
                            if (type == TabLaunchType.FROM_LONGPRESS_BACKGROUND
                                    || type == TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP
                                    || type == TabLaunchType.FROM_RECENT_TABS
                                            && !DeviceClassManager.enableAnimations()) {
                                Toast.makeText(
                                                ChromeTabbedActivity.this,
                                                R.string.open_in_new_tab_toast,
                                                Toast.LENGTH_SHORT)
                                        .show();
                            }
                        }
                    };
        } finally {
            TraceEvent.end("ChromeTabbedActivity.initializeCompositor");
        }
    }

    private void refreshSignIn() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.refreshSignIn")) {
            FirstRunSignInProcessor.openSyncSettingsIfScheduled(this);
            BackupSigninProcessor.start(this);
        }
    }

    private HubLayoutDependencyHolder createHubLayoutDependencyHolder() {
        // The tab_switcher_view_holder can be used on both tablet and phone because Hub's
        // animations don't depend on the compositor. This differs from the current tab switcher
        // behavior on phones.
        LazyOneshotSupplier<ViewGroup> rootViewSupplier =
                LazyOneshotSupplier.fromSupplier(
                        () -> {
                            ViewStub stub = findViewById(R.id.tab_switcher_view_holder_stub);
                            return (ViewGroup) stub.inflate();
                        });

        ObservableSupplier<Boolean> incognitoSupplier =
                new UnwrapObservableSupplier<>(
                        mTabModelSelector.getCurrentTabModelSupplier(),
                        (tabModel) -> tabModel == null ? false : tabModel.isIncognito());
        return new HubLayoutDependencyHolder(
                mHubProvider.getHubManagerSupplier(),
                rootViewSupplier,
                mRootUiCoordinator.getScrimCoordinator(),
                rootViewSupplier::get,
                incognitoSupplier,
                adaptOnToolbarAlphaChange());
    }

    private void setupCompositorContentForPhone() {
        if (isTablet()) return;

        try (TraceEvent e =
                TraceEvent.scoped("ChromeTabbedActivity.setupCompositorContentForPhone")) {
            CompositorViewHolder compositorViewHolder = getCompositorViewHolderSupplier().get();

            mLayoutManager =
                    new LayoutManagerChromePhone(
                            compositorViewHolder,
                            mContentContainer,
                            mTabSwitcherSupplier,
                            getTabModelSelectorSupplier(),
                            getTabContentManagerSupplier(),
                            mRootUiCoordinator::getTopUiThemeColorProvider,
                            createHubLayoutDependencyHolder());
            mLayoutStateProviderSupplier.set(mLayoutManager);
        }
    }

    private void setupCompositorContentForTablet() {
        if (!isTablet()) return;

        try (TraceEvent e =
                TraceEvent.scoped("ChromeTabbedActivity.setupCompositorContentForTablet")) {
            CompositorViewHolder compositorViewHolder = getCompositorViewHolderSupplier().get();

            ViewStub tabHoverCardViewStub = findViewById(R.id.tab_hover_card_holder_stub);
            View toolbarContainerView = findViewById(R.id.toolbar_container);
            mDragDropDelegate = new DragAndDropDelegateImpl();
            mDragDropDelegate.setDragAndDropBrowserDelegate(
                    new ChromeDragAndDropBrowserDelegate(() -> this));

            assert getToolbarManager() != null;

            ActionConfirmationManager actionConfirmationManager =
                    new ActionConfirmationManager(
                            mTabModelProfileSupplier.get(),
                            this,
                            null,
                            getModalDialogManagerSupplier().get());

            mLayoutManager =
                    new LayoutManagerChromeTablet(
                            compositorViewHolder,
                            mContentContainer,
                            mTabSwitcherSupplier,
                            getTabModelSelectorSupplier(),
                            getBrowserControlsManager(),
                            getTabContentManagerSupplier(),
                            mRootUiCoordinator::getTopUiThemeColorProvider,
                            mTabModelStartupInfoSupplier,
                            getLifecycleDispatcher(),
                            createHubLayoutDependencyHolder(),
                            mMultiInstanceManager,
                            mDragDropDelegate,
                            toolbarContainerView,
                            tabHoverCardViewStub,
                            getWindowAndroid(),
                            getToolbarManager(),
                            mRootUiCoordinator.getDesktopWindowStateProvider(),
                            actionConfirmationManager);
            mLayoutStateProviderSupplier.set(mLayoutManager);
        }
    }

    /** Returns the supplier for the {@link HubManager} for testing. */
    public OneshotSupplier<HubManager> getHubManagerSupplierForTesting() {
        return mHubManagerSupplier;
    }

    private void initHub() {
        mHubProvider =
                new HubProvider(
                        this,
                        getProfileProviderSupplier(),
                        new DefaultPaneOrderController(),
                        mBackPressManager,
                        getMenuOrKeyboardActionController(),
                        this::getSnackbarManager,
                        getTabModelSelectorSupplier(),
                        () -> getToolbarManager().getOverviewModeMenuButtonCoordinator());
        var builder = mHubProvider.getPaneListBuilder();
        builder.registerPane(
                PaneId.TAB_SWITCHER,
                LazyOneshotSupplier.fromSupplier(() -> createTabSwitcherPane(false)));
        builder.registerPane(
                PaneId.INCOGNITO_TAB_SWITCHER,
                LazyOneshotSupplier.fromSupplier(() -> createTabSwitcherPane(true)));
        if (TabUiFeatureUtilities.isTabGroupPaneEnabled()) {
            builder.registerPane(
                    PaneId.TAB_GROUPS, LazyOneshotSupplier.fromSupplier(this::createTabGroupsPane));
        }
        if (ChromeFeatureList.sCrossDeviceTabPaneAndroid.isEnabled()) {
            builder.registerPane(
                    PaneId.CROSS_DEVICE,
                    LazyOneshotSupplier.fromSupplier(this::createCrossDevicePane));
        }
        mHubProvider
                .getHubManagerSupplier()
                .onAvailable(manager -> mHubManagerSupplier.set(manager));
    }

    private @Nullable ObservableSupplier<Integer> getHubOverviewColorSupplier() {
        // Prior to Hub creation we don't know what color to use. Default to the background color
        // since this shouldn't be visible.
        ObservableSupplierImpl<Integer> overviewColorSupplier =
                new ObservableSupplierImpl<>(SemanticColorUtils.getDefaultBgColor(this));
        mHubManagerSupplier.onAvailable(
                (hubManager) -> {
                    ObservableSupplier<Pane> paneSupplier =
                            hubManager.getPaneManager().getFocusedPaneSupplier();
                    Callback<Pane> paneObserver =
                            pane ->
                                    overviewColorSupplier.set(
                                            hubManager.getHubController().getBackgroundColor(pane));
                    paneSupplier.addObserver(paneObserver);
                    mCleanUpHubOverviewColorObserver =
                            () -> paneSupplier.removeObserver(paneObserver);
                });
        return overviewColorSupplier;
    }

    private Pane createTabSwitcherPane(boolean isIncognito) {
        Pair<TabSwitcher, Pane> result =
                TabManagementDelegateProvider.getDelegate()
                        .createTabSwitcherPane(
                                this,
                                getLifecycleDispatcher(),
                                getProfileProviderSupplier(),
                                getTabModelSelector(),
                                getTabContentManager(),
                                getTabCreatorManagerSupplier().get(),
                                getBrowserControlsManager(),
                                getMultiWindowModeStateDispatcher(),
                                mRootUiCoordinator.getScrimCoordinator(),
                                getSnackbarManager(),
                                getModalDialogManager(),
                                mRootUiCoordinator.getBottomSheetController(),
                                mRootUiCoordinator.getDataSharingTabManager(),
                                mRootUiCoordinator.getIncognitoReauthControllerSupplier(),
                                mNewTabButtonClickListener,
                                isIncognito,
                                adaptOnToolbarAlphaChange(),
                                mBackPressManager);
        if (didFinishNativeInitialization()) {
            result.first.initWithNative();
        }
        if (isIncognito) {
            mIncognitoTabSwitcherSupplier.set(result.first);
        } else {
            mTabSwitcherSupplier.set(result.first);
        }
        return result.second;
    }

    private Pane createTabGroupsPane() {
        return TabManagementDelegateProvider.getDelegate()
                .createTabGroupsPane(
                        this,
                        getTabModelSelector(),
                        adaptOnToolbarAlphaChange(),
                        getProfileProviderSupplier(),
                        mHubProvider.getHubManagerSupplier(),
                        () ->
                                ((TabbedRootUiCoordinator) mRootUiCoordinator)
                                        .getTabGroupSyncController(),
                        getModalDialogManagerSupplier());
    }

    private Pane createCrossDevicePane() {
        return CrossDevicePaneFactory.create(this, adaptOnToolbarAlphaChange());
    }

    private void setupCompositorContent() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.setupCompositorContent")) {
            if (!isLayoutManagerCreated()) {
                if (isTablet()) {
                    setupCompositorContentForTablet();
                } else {
                    setupCompositorContentForPhone();
                }
            }

            mLayoutManager.setEnableAnimations(DeviceClassManager.enableAnimations());
        }
    }

    private void initializeCompositorContent() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.initializeCompositorContent")) {
            // TODO(yusufo): get rid of findViewById(R.id.url_bar).
            initializeCompositorContent(
                    mLayoutManager, findViewById(R.id.url_bar), mControlContainer);
        }
    }

    private boolean isLayoutManagerCreated() {
        return mLayoutManager != null;
    }

    private void onTabSwitcherClicked() {
        Profile profile = mTabModelProfileSupplier.get();
        if (profile != null) {
            TrackerFactory.getTrackerForProfile(profile)
                    .notifyEvent(EventConstants.TAB_SWITCHER_BUTTON_CLICKED);
        }

        if (getFullscreenManager().getPersistentFullscreenMode()) {
            return;
        }

        ReturnToChromeUtil.recordClickTabSwitcher(getTabModelSelector().getCurrentTab());

        showOverview();
    }

    private void initializeToolbarManager() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.initializeToolbarManager")) {
            mUndoBarPopupController.initialize();

            OnClickListener bookmarkClickHandler =
                    v -> mTabBookmarkerSupplier.get().addOrEditBookmark(getActivityTab());

            Profile profile = mTabModelProfileSupplier.get();
            ObservableSupplier<Integer> archivedTabCountSupplier =
                    ArchivedTabModelOrchestrator.getForProfile(profile).getTabCountSupplier();
            getToolbarManager()
                    .initializeWithNative(
                            mLayoutManager,
                            mLayoutManager.getStripLayoutHelperManager(),
                            v -> onTabSwitcherClicked(),
                            bookmarkClickHandler,
                            /* customTabsBackClickHandler= */ null,
                            archivedTabCountSupplier);

            // TODO(crbug.com/40828084): Fix this assert which is tripping on unrelated
            // tests.
            // assert !(mOverviewModeController != null
            //         && mOverviewModeController.overviewVisible());
        }
    }

    private void maybeCreateIncognitoTabSnapshotController() {
        try (TraceEvent e =
                TraceEvent.scoped(
                        "ChromeTabbedActivity.maybeCreateIncognitoTabSnapshotController")) {
            if (!CommandLine.getInstance()
                    .hasSwitch(ChromeSwitches.ENABLE_INCOGNITO_SNAPSHOTS_IN_ANDROID_RECENTS)) {
                IncognitoTabbedSnapshotController.createIncognitoTabSnapshotController(
                        this, mLayoutManager, mTabModelSelector, getLifecycleDispatcher());
            }

            mUIWithNativeInitialized = true;

            // The dataset has already been created, we need to initialize our state.
            mTabModelSelector.notifyChanged();

            // Check for incognito tabs to handle the case where Chrome was swiped away in the
            // background.
            if (!IncognitoTabHostUtils.doIncognitoTabsExist()) {
                IncognitoNotificationManager.dismissIncognitoNotification();
                DownloadNotificationService.getInstance().cancelOffTheRecordDownloads();
            }
        }
    }

    private void maybeGetFeedAppLifecycleAndMaybeCreatePageViewObserver() {
        try (TraceEvent e =
                TraceEvent.scoped(
                        "ChromeTabbedActivity."
                                + "maybeGetFeedAppLifecycleAndMaybeCreatePageViewObserver")) {
            FeedSurfaceTracker.getInstance().startup();

            getProfileProviderSupplier()
                    .runSyncOrOnAvailable(
                            (profileProvider) -> {
                                UsageStatsService.createPageViewObserverIfEnabled(
                                        this,
                                        profileProvider.getOriginalProfile(),
                                        getActivityTabProvider(),
                                        getTabContentManagerSupplier());
                            });
        }
    }

    @Override
    protected OneshotSupplier<ProfileProvider> createProfileProvider() {
        return new ActivityProfileProvider(getLifecycleDispatcher());
    }

    @Override
    public void startNativeInitialization() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.startNativeInitialization")) {
            startUmaSession();
            // This is on the critical path so don't delay.
            setupCompositorContent();
            if (!DeviceFormFactor.isTablet()) {
                PostTask.postTask(
                        TaskTraits.UI_DEFAULT,
                        mCallbackController.makeCancelable(this::initializeCompositorContent));
            } else {
                // TODO(crbug.com/40853081): Enable split compositor task on tablets.
                initializeCompositorContent();
            }

            // All this initialization can be expensive so it's split into multiple tasks.
            PostTask.postTask(
                    TaskTraits.UI_DEFAULT, mCallbackController.makeCancelable(this::refreshSignIn));
            PostTask.postTask(
                    TaskTraits.UI_DEFAULT,
                    mCallbackController.makeCancelable(this::initializeToolbarManager));
            PostTask.postTask(
                    TaskTraits.UI_DEFAULT,
                    mCallbackController.makeCancelable(
                            this::maybeCreateIncognitoTabSnapshotController));
            // Always call into this function, even if BackPressManager is disabled to initialize
            // back press managers which reduce code duplication in this class.
            PostTask.postTask(TaskTraits.UI_DEFAULT, this::initializeBackPressHandlers);
            PostTask.postTask(
                    TaskTraits.UI_DEFAULT,
                    mCallbackController.makeCancelable(
                            this::maybeGetFeedAppLifecycleAndMaybeCreatePageViewObserver));
            PostTask.postTask(
                    TaskTraits.UI_DEFAULT,
                    mCallbackController.makeCancelable(this::finishNativeInitialization));
        }
    }

    @Override
    public void finishNativeInitialization() {
        try (TraceEvent te = TraceEvent.scoped("ChromeTabbedActivity.finishNativeInitialization")) {
            assert getProfileProviderSupplier().hasValue();
            new NavigationPredictorBridge(
                    getProfileProviderSupplier().get().getOriginalProfile(),
                    getLifecycleDispatcher(),
                    this::isWarmOnResume);

            super.finishNativeInitialization();

            // TODO(jinsukkim): Let these classes handle the registration by themselves.
            mCompositorViewHolder = getCompositorViewHolderSupplier().get();
            getTabObscuringHandler().addObserver(mCompositorViewHolder);

            ChromeAccessibilityUtil.get().addObserver(mLayoutManager);
            if (isTablet()) {
                ChromeAccessibilityUtil.get().addObserver(mCompositorViewHolder);
            }

            TabSwitcher switcher = mTabSwitcherSupplier.get();
            if (switcher != null) {
                switcher.initWithNative();
            }
            TabSwitcher incognitoSwitcher = mIncognitoTabSwitcherSupplier.get();
            if (incognitoSwitcher != null) {
                incognitoSwitcher.initWithNative();
            }

            mInactivityTracker.setLastVisibleTimeMsAndRecord(System.currentTimeMillis());

            getSnackbarManager().setEdgeToEdgeSupplier(getEdgeToEdgeSupplier().get());
        }
    }

    @Override
    public void onResumeWithNative() {
        // While the super#onResumeWithNative call below also invokes #startUmaSession, we call
        // it here (early) as well to setup the UMA activity state for any metrics emitted prior
        // to call to super#onResumeWithNative below. It's safe to call #startUmaSession in both
        // places as a session will only be started if one is not already running.
        startUmaSession();

        // On warm startup, call setInitialOverviewState in onResume() instead of onStart(). This is
        // because onResume() is guaranteed to called after onNewIntent() and thus have the updated
        // Intent which is used by shouldShowOverviewPageOnStart(). See https://crbug.com/1321607.
        if (mFromResumption) {
            setInitialOverviewState();
        } else {
            // Set mFromResumption to be true to skip the call of setInitialOverviewState() in
            // onStart() when the next time onStart() is called, since it is no longer a cold start.
            mFromResumption = true;
        }

        super.onResumeWithNative();

        IncognitoStartup.onResumeWithNative(
                getTabModelSelectorSupplier(), TABBED_MODE_COMPONENT_NAMES);

        mLocaleManager.setSnackbarManager(getSnackbarManager());
        mLocaleManager.startObservingPhoneChanges();

        // This call is not guarded by a feature flag.
        SearchEngineChoiceNotification.handleSearchEngineChoice(this, getSnackbarManager());

        if (!isWarmOnResume()) {
            getProfileProviderSupplier()
                    .runSyncOrOnAvailable(
                            (profileProvider) -> {
                                SuggestionsMetrics.recordArticlesListVisible(
                                        profileProvider.getOriginalProfile());
                            });
        } else {
            mInactivityTracker.setLastVisibleTimeMsAndRecord(System.currentTimeMillis());
        }
    }

    @Override
    public void onPauseWithNative() {
        mTabModelSelector.commitAllTabClosures();
        CookiesFetcher.persistCookies();

        mLocaleManager.setSnackbarManager(null);
        mLocaleManager.stopObservingPhoneChanges();

        // Always track the last backgrounded time in case others are using the pref.
        mInactivityTracker.setLastBackgroundedTimeInPrefs(System.currentTimeMillis());

        super.onPauseWithNative();
    }

    @Override
    public void onStopWithNative() {
        super.onStopWithNative();

        saveState();
        mHasDeterminedOverviewStateForCurrentSession = false;
    }

    @Override
    public void onStartWithNative() {
        // While the super#onStartWithNative call below also invokes #startUmaSession, we call
        // it here (early) as well to setup the UMA activity state for any metrics emitted prior
        // to call to super#onStartWithNative below. It's safe to call #startUmaSession in both
        // places as a session will only be started if one is not already running.
        startUmaSession();

        mMainIntentMetrics.logLaunchBehavior();

        super.onStartWithNative();

        // Don't call setInitialOverviewState if 1) we're waiting for the tab's creation or we risk
        // showing a glimpse of the tab selector during start up. 2) on warm startup from an
        // resumption. Defer it to onResumeWitheNative() since it needs to check the latest Intent
        // which is only guaranteed to be updated onResume() if onNewIntent() is called.
        if (!mPendingInitialTabCreation && !mFromResumption) {
            setInitialOverviewState();
        }

        Bundle savedInstanceState = getSavedInstanceState();
        if (savedInstanceState != null
                && savedInstanceState.getBoolean(IS_INCOGNITO_SELECTED, false)) {
            // This will be executed only once since SavedInstanceState will be reset a few lines
            // later.
            AndroidSessionDurationsServiceState.restoreNativeFromSerialized(
                    savedInstanceState,
                    getCurrentTabModel()
                            .getProfile()
                            .getPrimaryOTRProfile(/* createIfNeeded= */ true));
        }

        resetSavedInstanceState();
        BookmarkUtils.maybeExpireLastBookmarkLocationForReadLater(
                mInactivityTracker.getTimeSinceLastBackgroundedMs());

        MultiWindowUtils.maybeRecordDesktopWindowCountHistograms(
                mRootUiCoordinator.getDesktopWindowStateProvider(),
                mInstanceAllocationType,
                !mFromResumption);
    }

    @Override
    public void onNewIntent(Intent intent) {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onNewIntent")) {
            mIntentHandlingTimeMs = SystemClock.uptimeMillis();

            // Drop the cleaner intent since it's created in order to clear up the OS share sheet.
            if (ShareHelper.isCleanerIntent(intent)) {
                return;
            }

            // The intent to use in maybeDispatchExplicitMainViewIntent(). We're explicitly
            // adding NEW_TASK flag to make sure backing from CCT brings up the caller activity,
            // and not Chrome
            Intent intentForDispatching = new Intent(intent);
            intentForDispatching.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            @LaunchIntentDispatcher.Action
            int action =
                    maybeDispatchExplicitMainViewIntent(
                            intentForDispatching, DispatchedBy.ON_NEW_INTENT);
            if (action != LaunchIntentDispatcher.Action.CONTINUE) {
                // Pressing back button in CCT should bring user to the caller activity.
                moveTaskToBack(true);
                // Intent was dispatched to CustomTabActivity, consume it.
                return;
            }

            super.onNewIntent(intent);

            boolean shouldShowRegularOverviewMode =
                    IntentUtils.safeGetBooleanExtra(
                            intent, IntentHandler.EXTRA_OPEN_REGULAR_OVERVIEW_MODE, false);
            if (shouldShowRegularOverviewMode && IntentHandler.wasIntentSenderChrome(intent)) {
                mTabModelSelector.selectModel(/* incognito= */ false);
                mLayoutManager.showLayout(LayoutType.TAB_SWITCHER, /* animate= */ false);
            }
            // Launch history on an already running instance of Chrome.
            maybeLaunchHistory();
        }
    }

    @Override
    public void onNewIntentWithNative(Intent intent) {
        try {
            TraceEvent.begin("ChromeTabbedActivity.onNewIntentWithNative");

            super.onNewIntentWithNative(intent);
            if (!IntentHandler.shouldIgnoreIntent(intent, this, /* isCustomTab= */ false)) {
                maybeHandleUrlIntent(intent);
            }

            if (IntentUtils.isMainIntentFromLauncher(intent)) {
                logMainIntentBehavior(intent);
            }

            if (CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_TEST_INTENTS)) {
                handleDebugIntent(intent);
            }

            // Launch tab switcher if it is a data sharing intent.
            maybeShowTabSwitcherAfterTabModelLoad(intent);
        } finally {
            TraceEvent.end("ChromeTabbedActivity.onNewIntentWithNative");
        }
    }

    /**
     * @param intent The received intent.
     * @return Whether the Intent was successfully handled.
     */
    private boolean maybeHandleUrlIntent(Intent intent) {
        String url = IntentHandler.getUrlFromIntent(intent);
        @TabOpenType int tabOpenType = IntentHandler.getTabOpenType(intent);
        int tabIdToBringToFront = IntentHandler.getBringTabToFrontId(intent);
        if (url == null && tabIdToBringToFront == Tab.INVALID_TAB_ID) return false;

        LoadUrlParams loadUrlParams =
                IntentHandler.createLoadUrlParamsForIntent(url, intent, mIntentHandlingTimeMs);

        if (IntentHandler.isIntentForMhtmlFileOrContent(intent)
                && tabOpenType == TabOpenType.OPEN_NEW_TAB
                && loadUrlParams.getReferrer() == null
                && loadUrlParams.getVerbatimHeaders() == null) {
            getProfileProviderSupplier()
                    .runSyncOrOnAvailable(
                            (profileProvider) -> {
                                handleMhtmlFileOrContentIntent(
                                        profileProvider.getOriginalProfile(), url, intent);
                            });
            return true;
        }
        processUrlViewIntent(
                loadUrlParams,
                tabOpenType,
                IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID),
                tabIdToBringToFront,
                intent);
        return true;
    }

    private void handleMhtmlFileOrContentIntent(
            final Profile profile, final String url, final Intent intent) {
        OfflinePageUtils.getLoadUrlParamsForOpeningMhtmlFileOrContent(
                url,
                (loadUrlParams) -> {
                    loadUrlParams.setVerbatimHeaders(
                            IntentHandler.maybeAddAdditionalContentHeaders(
                                    intent, url, loadUrlParams.getVerbatimHeaders()));
                    processUrlViewIntent(
                            loadUrlParams,
                            TabOpenType.OPEN_NEW_TAB,
                            null,
                            Tab.INVALID_TAB_ID,
                            intent);
                },
                profile);
    }

    @Override
    public @ActivityType int getActivityType() {
        return ActivityType.TABBED;
    }

    @Override
    public ChromeTabCreator getTabCreator(boolean incognito) {
        return (ChromeTabCreator) super.getTabCreator(incognito);
    }

    @Override
    public ChromeTabCreator getCurrentTabCreator() {
        return (ChromeTabCreator) super.getCurrentTabCreator();
    }

    private void handleDebugIntent(Intent intent) {
        if (ACTION_CLOSE_TABS.equals(intent.getAction())) {
            CloseAllTabsHelper.closeAllTabsHidingTabGroups(getTabModelSelectorSupplier().get());
        } else if (MemoryPressureListener.handleDebugIntent(
                ChromeTabbedActivity.this, intent.getAction())) {
            // Handled.
        }
    }

    private void setTrackColdStartupMetrics(boolean shouldTrackColdStartupMetrics) {
        assert getLegacyTabStartupMetricsTracker() != null;
        assert getStartupMetricsTracker() != null;

        if (shouldTrackColdStartupMetrics) {
            getLegacyTabStartupMetricsTracker().setHistogramSuffix(ActivityType.TABBED);
            getStartupMetricsTracker().setHistogramSuffix(ActivityType.TABBED);
        } else {
            getLegacyTabStartupMetricsTracker().cancelTrackingStartupMetrics();
        }
    }

    private void setInitialOverviewState() {
        if (mFromResumption) {
            setInitialOverviewStateWithNtp();
        }
    }

    /**
     * Called on warm startup to show a home surface NTP instead of the last active Tab if the user
     * has left Chrome for a while.
     */
    private void setInitialOverviewStateWithNtp() {
        boolean showedNtp =
                ReturnToChromeUtil.setInitialOverviewStateOnResumeWithNtp(
                        mTabModelSelector.isIncognitoSelected(),
                        shouldShowNtpHomeSurfaceOnStartup(),
                        getCurrentTabModel(),
                        getTabCreator(false),
                        mHomeSurfaceTracker);
        if (showedNtp && mLayoutManager.isLayoutVisible(LayoutType.TAB_SWITCHER)) {
            mLayoutManager.showLayout(LayoutType.BROWSING, /* animate= */ false);
        }
    }

    private void logMainIntentBehavior(Intent intent) {
        assert IntentUtils.isMainIntentFromLauncher(intent);
        // TODO(tedchoc): We should cache the last visible time and reuse it to avoid different
        //                values of this depending on when it is called after the activity was
        //                shown.

        // Temporary safety check to make sure none of this code runs if the feature is
        // disabled.
        if (ReengagementNotificationController.isEnabled()) {
            if (mCallbackController != null) {
                new OneShotCallback<>(
                        mTabModelProfileSupplier,
                        mCallbackController.makeCancelable(
                                profile -> {
                                    assert profile != null
                                            : "Unexpectedly null profile from TabModel.";
                                    if (profile == null) return;

                                    TrackerFactory.getTrackerForProfile(profile)
                                            .notifyEvent(EventConstants.STARTED_FROM_MAIN_INTENT);
                                }));
            }
        }

        mMainIntentMetrics.onMainIntentWithNative(
                mInactivityTracker.getTimeSinceLastBackgroundedMs());
    }

    /** Access the main intent metrics for test validation. */
    public MainIntentBehaviorMetrics getMainIntentBehaviorMetricsForTesting() {
        return mMainIntentMetrics;
    }

    public ChromeInactivityTracker getInactivityTrackerForTesting() {
        return mInactivityTracker;
    }

    @Override
    public void initializeState() {
        // This method goes through 3 steps:
        // 1. Load the saved tab state (but don't start restoring the tabs yet).
        // 2. Process the Intent that this activity received and if that should result in any
        //    new tabs, create them.  This is done after step 1 so that the new tab gets
        //    created after previous tab state was restored.
        // 3. If no tabs were created in any of the above steps, create an NTP, otherwise
        //    start asynchronous tab restore (loading the previously active tab synchronously
        //    if no new tabs created in step 2).

        // Only look at the original intent if this is not a "restoration" and we are allowed to
        // process intents. Any subsequent intents are carried through onNewIntent.
        try {
            TraceEvent.begin("ChromeTabbedActivity.initializeState");

            super.initializeState();
            Log.i(TAG, "#initializeState");
            Intent intent = getIntent();

            boolean hadCipherData =
                    CipherFactory.getInstance().restoreFromBundle(getSavedInstanceState());

            boolean noRestoreState =
                    CommandLine.getInstance().hasSwitch(ChromeSwitches.NO_RESTORE_STATE);
            boolean shouldShowNtpAsHomeSurfaceAtStartup = false;
            final AtomicBoolean isActiveUrlNtp = new AtomicBoolean(false);
            if (noRestoreState) {
                // Clear the state files because they are inconsistent and useless from now on.
                mTabModelOrchestrator.clearState();
            } else {
                // State should be clear when we start first run and hence we do not need to load
                // a previous state. This may change the current Model, watch out for initialization
                // based on the model.
                // Never attempt to restore incognito tabs when this activity was previously swiped
                // away in Recents. http://crbug.com/626629
                boolean ignoreIncognitoFiles = !hadCipherData;

                // If the home surface should be shown on startup, check if the active tab restored
                // from disk is an NTP that can be reused for Start.
                Callback<String> onStandardActiveIndexRead = null;
                shouldShowNtpAsHomeSurfaceAtStartup = shouldShowNtpHomeSurfaceOnStartup();
                mHomeSurfaceTracker = new HomeSurfaceTracker();
                if (shouldShowNtpAsHomeSurfaceAtStartup) {
                    onStandardActiveIndexRead =
                            url -> {
                                mLastActiveTabUrl = url;
                                if (UrlUtilities.isNtpUrl(url)) {
                                    assert !mTabModelSelector.isIncognitoSelected();
                                    isActiveUrlNtp.set(true);
                                }
                            };
                }
                mTabModelOrchestrator.loadState(ignoreIncognitoFiles, onStandardActiveIndexRead);
            }

            getProfileProviderSupplier()
                    .onAvailable(
                            (profileProvider) -> {
                                if (isActivityFinishingOrDestroyed()) return;
                                mAuxiliarySearchController =
                                        AuxiliarySearchControllerFactory
                                                .createAuxiliarySearchController(
                                                        profileProvider.getOriginalProfile(),
                                                        mTabModelSelector);
                                if (mAuxiliarySearchController != null) {
                                    mAuxiliarySearchController.register(
                                            this.getLifecycleDispatcher());
                                }
                            });

            mInactivityTracker.register(this.getLifecycleDispatcher());
            boolean isIntentWithEffect = false;
            boolean isMainIntentFromLauncher = false;
            boolean isLaunchingDraggedTab = false;
            if (getSavedInstanceState() == null && intent != null) {
                if (!shouldIgnoreIntent()) {
                    isLaunchingDraggedTab = maybeLaunchDraggedTabInWindow(intent);
                    // If launching tab drag was successful, ignore handling url intent.
                    isIntentWithEffect = isLaunchingDraggedTab || maybeHandleUrlIntent(intent);
                }

                if (IntentUtils.isMainIntentFromLauncher(intent)) {
                    isMainIntentFromLauncher = true;
                    logMainIntentBehavior(intent);
                }
            }

            mIntentMetadataOneshotSupplier.set(
                    new ToolbarIntentMetadata(isMainIntentFromLauncher, isIntentWithEffect));

            // If we have tabs to reparent and getSavedInstanceState() is non-null, then the tabs
            // are coming from night mode tab reparenting. In this case, reparenting happens
            // synchronously along with tab restoration so there are no tabs waiting for
            // reparenting like there are for other tab reparenting operations.
            // Reparenting is also triggered when the intent launches the current window from a
            // dragged tab.
            boolean hasTabWaitingForReparenting =
                    (AsyncTabParamsManagerSingleton.getInstance().hasParamsWithTabToReparent()
                                    && getSavedInstanceState() == null)
                            || isLaunchingDraggedTab;
            mCreatedTabOnStartup =
                    getCurrentTabModel().getCount() > 0
                            || mTabModelOrchestrator.getRestoredTabCount() > 0
                            || isIntentWithEffect
                            || hasTabWaitingForReparenting;

            // We always need to try to restore tabs. The set of tabs might be empty, but at least
            // it will trigger the notification that tab restore is complete which is needed by
            // other parts of Chrome such as sync.
            boolean activeTabBeingRestored = !isIntentWithEffect;

            if (shouldShowNtpAsHomeSurfaceAtStartup
                    && !isIntentWithEffect
                    && !hasTabWaitingForReparenting) {
                // If a home surface should be shown at startup on tablets and the last active Tab
                // is a NTP, we will reuse it to show the home surface UI. Otherwise, we'll create
                // one, and set it as the active Tab. |mLastActiveTabUrl| is null when there isn't
                // any Tab.
                if (!isActiveUrlNtp.get() && mLastActiveTabUrl != null) {
                    ReturnToChromeUtil.createNewTabAndShowHomeSurfaceUi(
                            getTabCreator(false),
                            mHomeSurfaceTracker,
                            mTabModelSelector,
                            mLastActiveTabUrl,
                            null);
                    activeTabBeingRestored = false;
                    mCreatedTabOnStartup = true;
                    mLastActiveTabUrl = null;
                }
                ReturnToChromeUtil.recordHomeSurfaceShownAtStartup();
                ReturnToChromeUtil.recordHomeSurfaceShown();
            }

            mTabModelOrchestrator.restoreTabs(activeTabBeingRestored);

            // Only create an initial tab if no tabs were restored and no intent was handled.
            // Also, check whether the active tab was supposed to be restored and that the total
            // tab count is now non zero.  If this is not the case, tab restore failed and we need
            // to create a new tab as well.
            if (!mCreatedTabOnStartup
                    || (!hasTabWaitingForReparenting
                            && activeTabBeingRestored
                            && getTabModelSelector().getTotalTabCount() == 0)) {
                // If homepage URI is not determined, due to PartnerBrowserCustomizations provider
                // async reading, then create a tab at the async reading finished. If it takes
                // too long, just create NTP.
                mPendingInitialTabCreation = true;
                PartnerBrowserCustomizations.getInstance()
                        .setOnInitializeAsyncFinished(
                                () -> {
                                    if (!isActivityFinishingOrDestroyed()) {
                                        createInitialTab();
                                    }
                                },
                                INITIAL_TAB_CREATION_TIMEOUT_MS);
            }

            // If initial tab creation is pending, this will instead be handled when we create the
            // initial tab in #createInitialTab.
            if (!mPendingInitialTabCreation) {
                Tab currentTab = getActivityTab();
                boolean isTabNtp = isTabRegularNtp(currentTab);
                if (isTabNtp && !currentTab.isNativePage()) {
                    // This will be a NTP, but the native page hasn't been created yet. Need to wait
                    // for this to be created before allowing the toolbar to draw.
                    currentTab.addObserver(
                            new EmptyTabObserver() {
                                @Override
                                public void onContentChanged(Tab tab) {
                                    tab.removeObserver(this);
                                    mAppLaunchDrawBlocker.onActiveTabAvailable(
                                            /* isTabNtp= */ true);
                                }
                            });
                } else {
                    mAppLaunchDrawBlocker.onActiveTabAvailable(isTabNtp);
                }
                // Launch history as a resumption of a previous Chrome journey.
                maybeLaunchHistory();
            }

            if (getSavedInstanceState() != null) {
                long unfoldLatencyBeginTime =
                        getSavedInstanceState()
                                .getLong(ChromeActivity.UNFOLD_LATENCY_BEGIN_TIMESTAMP);
                if (unfoldLatencyBeginTime != 0) {
                    getWindowAndroid().setUnfoldLatencyBeginTime(unfoldLatencyBeginTime);
                }
            }

            maybeShowTabSwitcherAfterTabModelLoad(intent);
        } finally {
            TraceEvent.end("ChromeTabbedActivity.initializeState");
        }
    }

    private boolean hasStartWithNativeBeenCalled() {
        int activity_state = getLifecycleDispatcher().getCurrentActivityState();
        return activity_state == ActivityLifecycleDispatcher.ActivityState.STARTED_WITH_NATIVE
                || activity_state == ActivityLifecycleDispatcher.ActivityState.RESUMED_WITH_NATIVE;
    }

    /** Create an initial tab for cold start without restored tabs. */
    private void createInitialTab() {
        Log.i(TAG, "#createInitialTab executed.");
        mPendingInitialTabCreation = false;

        String url = null;
        GURL homepageGurl = HomepageManager.getInstance().getHomepageGurl();
        if (homepageGurl.isEmpty()) {
            url = UrlConstants.NTP_URL;
        } else {
            // Migrate legacy NTP URLs (chrome://newtab) to the newer format
            // (chrome-native://newtab)
            if (UrlUtilities.isNtpUrl(homepageGurl)) {
                url = UrlConstants.NTP_URL;
            } else {
                url = homepageGurl.getSpec();
            }
        }
        getTabCreator(false).launchUrl(url, TabLaunchType.FROM_STARTUP);
        PartnerBrowserCustomizations.getInstance()
                .onCreateInitialTab(
                        url,
                        getLifecycleDispatcher(),
                        HomepageManager::getHomepageCharacterizationHelper);

        // If we didn't call setInitialOverviewState() in onStartWithNative() because
        // mPendingInitialTabCreation was true then do so now.
        if (hasStartWithNativeBeenCalled()) {
            setInitialOverviewState();
        }

        mAppLaunchDrawBlocker.onActiveTabAvailable(isTabRegularNtp(getActivityTab()));
        // Launch history as a fresh instance of Chrome.
        maybeLaunchHistory();
    }

    private void recordExternalIntentSourceUMA(Intent intent) {
        @IntentHandler.ExternalAppId
        int externalId = IntentHandler.determineExternalIntentSource(intent, this);

        // Don't record external app page loads for intents we sent.
        if (externalId == IntentHandler.ExternalAppId.CHROME) return;
        RecordHistogram.recordEnumeratedHistogram(
                "MobileIntent.PageLoadDueToExternalApp",
                externalId,
                IntentHandler.ExternalAppId.NUM_ENTRIES);
    }

    /**
     * Records an action when a user chose to handle a URL in Chrome that could have been handled
     * by an application installed on the phone. Also records the name of that application.
     * This doesn't include generic URL handlers, such as browsers.
     */
    private static void recordAppHandlersForIntent(Intent intent) {
        List<String> packages =
                IntentUtils.safeGetStringArrayListExtra(
                        intent, ExternalNavigationHandler.EXTRA_EXTERNAL_NAV_PACKAGES);
        if (packages != null && packages.size() > 0) {
            RecordUserAction.record("MobileExternalNavigationReceived");
        }
    }

    /**
     * Processes a url view intent.
     *
     * @param url The url from the intent.
     */
    private void processUrlViewIntent(
            LoadUrlParams loadUrlParams,
            @TabOpenType int tabOpenType,
            String externalAppId,
            int tabIdToBringToFront,
            Intent intent) {
        if (isActivityFinishingOrDestroyed()) {
            return;
        }
        if (isProbablyFromChrome(intent, externalAppId)) {
            RecordUserAction.record("MobileTabbedModeViewIntentFromChrome");
        } else {
            RecordUserAction.record("MobileTabbedModeViewIntentFromApp");
        }

        recordExternalIntentSourceUMA(intent);
        recordAppHandlersForIntent(intent);

        final String url = loadUrlParams.getUrl();
        boolean fromLauncherShortcut =
                IntentUtils.safeGetBooleanExtra(
                        intent, IntentHandler.EXTRA_INVOKED_FROM_SHORTCUT, false);
        boolean fromAppWidget =
                IntentUtils.safeGetBooleanExtra(
                        intent, IntentHandler.EXTRA_INVOKED_FROM_APP_WIDGET, false);
        boolean focus = false;

        TabModel tabModel = getCurrentTabModel();
        switch (tabOpenType) {
            case TabOpenType.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB:
                mTabModelOrchestrator.tryToRestoreTabStateForUrl(url);
                int tabToBeClobberedIndex = TabModelUtils.getTabIndexByUrl(tabModel, url);
                Tab tabToBeClobbered = tabModel.getTabAt(tabToBeClobberedIndex);
                if (tabToBeClobbered != null) {
                    TabModelUtils.setIndex(tabModel, tabToBeClobberedIndex);
                    tabToBeClobbered.reload();
                } else {
                    launchIntent(loadUrlParams, externalAppId, true, intent);
                }
                int shortcutSource =
                        intent.getIntExtra(WebappConstants.EXTRA_SOURCE, ShortcutSource.UNKNOWN);
                LaunchMetrics.recordHomeScreenLaunchIntoTab(url, shortcutSource);
                if (fromAppWidget && UrlConstants.CHROME_DINO_URL.equals(url)) {
                    RecordUserAction.record("QuickActionSearchWidget.StartDinoGame");
                }
                break;
            case TabOpenType.BRING_TAB_TO_FRONT:
                mTabModelOrchestrator.tryToRestoreTabStateForId(tabIdToBringToFront);

                int tabIndex = TabModelUtils.getTabIndexById(tabModel, tabIdToBringToFront);
                if (tabIndex == TabModel.INVALID_TAB_INDEX) {
                    TabModel otherModel = getTabModelSelector().getModel(!tabModel.isIncognito());
                    tabIndex = TabModelUtils.getTabIndexById(otherModel, tabIdToBringToFront);
                    if (tabIndex != TabModel.INVALID_TAB_INDEX) {
                        getTabModelSelector().selectModel(otherModel.isIncognito());
                        TabModelUtils.setIndex(otherModel, tabIndex);
                    } else {
                        Log.e(TAG, "Failed to bring tab to front because it doesn't exist.");
                        return;
                    }
                } else {
                    TabModelUtils.setIndex(tabModel, tabIndex);
                }
                break;
            case TabOpenType.CLOBBER_CURRENT_TAB:
                // The browser triggered the intent. This happens when clicking links which
                // can be handled by other applications (e.g. www.youtube.com links).
                Tab currentTab = getActivityTab();
                if (currentTab != null) {
                    RedirectHandlerTabHelper.updateIntentInTab(currentTab, intent);
                    currentTab.loadUrl(loadUrlParams);
                } else {
                    launchIntent(loadUrlParams, externalAppId, true, intent);
                }
                break;
            case TabOpenType.REUSE_APP_ID_MATCHING_TAB_ELSE_NEW_TAB:
                launchIntent(loadUrlParams, externalAppId, false, intent);
                break;
            case TabOpenType.REUSE_TAB_MATCHING_ID_ELSE_NEW_TAB:
                int tabId =
                        IntentUtils.safeGetIntExtra(
                                intent,
                                TabOpenType.REUSE_TAB_MATCHING_ID_STRING,
                                Tab.INVALID_TAB_ID);
                if (tabId != Tab.INVALID_TAB_ID) {
                    mTabModelOrchestrator.tryToRestoreTabStateForId(tabId);
                    int matchingTabIndex = TabModelUtils.getTabIndexById(tabModel, tabId);
                    boolean loaded = false;
                    if (matchingTabIndex != TabModel.INVALID_TAB_INDEX) {
                        Tab tab = tabModel.getTabAt(matchingTabIndex);
                        String spec = tab.getUrl().getSpec();
                        if (spec.equals(url)
                                || spec.equals(
                                        IntentUtils.safeGetStringExtra(
                                                intent,
                                                TabOpenType.REUSE_TAB_ORIGINAL_URL_STRING))) {
                            tabModel.setIndex(matchingTabIndex, TabSelectionType.FROM_USER);
                            tab.loadUrl(loadUrlParams);
                            loaded = true;
                        }
                    }
                    if (!loaded) {
                        launchIntent(loadUrlParams, externalAppId, false, intent);
                    }
                }
                break;
            case TabOpenType.OPEN_NEW_TAB:
                if (fromLauncherShortcut) {
                    recordLauncherShortcutAction(false);
                    reportNewTabShortcutUsed(false);
                }

                launchIntent(loadUrlParams, externalAppId, true, intent);
                break;
            case TabOpenType.OPEN_NEW_INCOGNITO_TAB:
                if (!TextUtils.equals(externalAppId, getPackageName())) {
                    assert false : "Only Chrome is allowed to open incognito tabs";
                    Log.e(TAG, "Only Chrome is allowed to open incognito tabs");
                    return;
                }

                if (!IncognitoUtils.isIncognitoModeEnabled(
                        getProfileProviderSupplier().get().getOriginalProfile())) {
                    // The incognito launcher shortcut is manipulated in #onDeferredStartup(),
                    // so it's possible for a user to invoke the shortcut before it's disabled.
                    // Quick actions search widget is installed on the home screen and may
                    // need to be updated before the incognito button is removed.
                    // Opening an incognito tab while incognito mode is disabled from somewhere
                    // besides the launcher shortcut of from quick action search widget is an
                    // error.
                    if (fromAppWidget || fromLauncherShortcut) {
                        // We are using the message introduced for quick action search widget
                        // for both the widget and the launcher shortcut here.
                        Toast.makeText(
                                        ChromeTabbedActivity.this,
                                        R.string.quick_action_search_widget_message_no_incognito,
                                        Toast.LENGTH_LONG)
                                .show();
                    } else {
                        assert false : "Tried to open incognito tab while incognito disabled";
                        Log.e(TAG, "Tried to open incognito tab while incognito disabled");
                    }

                    return;
                }

                if (url == null || url.equals(UrlConstants.NTP_URL)) {
                    if (fromLauncherShortcut) {
                        getTabCreator(true)
                                .launchUrl(
                                        UrlConstants.NTP_URL, TabLaunchType.FROM_LAUNCHER_SHORTCUT);
                        recordLauncherShortcutAction(true);
                        reportNewTabShortcutUsed(true);
                    } else if (fromAppWidget) {
                        RecordUserAction.record("QuickActionSearchWidget.StartIncognito");
                        getTabCreator(true)
                                .launchUrl(UrlConstants.NTP_URL, TabLaunchType.FROM_APP_WIDGET);
                    } else if (IncognitoTabLauncher.didCreateIntent(intent)) {
                        Tab tab =
                                getTabCreator(true)
                                        .launchUrl(
                                                UrlConstants.NTP_URL,
                                                TabLaunchType.FROM_LAUNCH_NEW_INCOGNITO_TAB);
                        if (IncognitoTabLauncher.shouldFocusOmnibox(intent)) {
                            // Since the Tab is created in the foreground, its View will gain
                            // focus, and since the Tab and the URL bar are not yet in the same
                            // View hierarchy, setting the URL bar's focus here won't clear the
                            // Tab's focus. When the Tab is added to the hierarchy, we want the
                            // URL bar to retain focus, so we clear the Tab's focus here.
                            tab.getView().clearFocus();
                            focus = true;
                        }

                        IncognitoTabLauncher.recordUse();
                    } else {
                        // Used by the Account management screen to open a new incognito tab.
                        // Account management screen collects its metrics separately.
                        getTabCreator(true)
                                .launchUrl(
                                        UrlConstants.NTP_URL,
                                        TabLaunchType.FROM_CHROME_UI,
                                        intent,
                                        mIntentHandlingTimeMs);
                    }
                } else {
                    launchIntent(loadUrlParams, externalAppId, true, intent);
                }
                break;
            default:
                assert false : "Unknown TabOpenType: " + tabOpenType;
                break;
        }

        getToolbarManager()
                .setUrlBarFocusOnceNativeInitialized(
                        focus,
                        focus
                                ? OmniboxFocusReason.LAUNCH_NEW_INCOGNITO_TAB
                                : OmniboxFocusReason.UNFOCUS);

        if (tabModel.getCount() > 0 && isInOverviewMode() && !isTablet()) {
            // Hides the overview page to ensure proper layout change signals are sent.
            hideOverview();
        }
    }

    private boolean isProbablyFromChrome(Intent intent, String externalAppId) {
        // To determine if the processed intent is from Chrome, check for any of the following:
        // 1.) The authentication token that will be added to trusted intents.
        // 2.) The app ID matches Chrome.  This value can be spoofed by other applications, but
        //     in cases where we were not able to add the authentication token this is our only
        //     indication the intent was from Chrome.
        return IntentHandler.wasIntentSenderChrome(intent)
                || TextUtils.equals(externalAppId, getPackageName());
    }

    private void maybeLaunchHistory() {
        // Can be launched as (1) a fresh instance of Chrome (2) a new intent on an already running
        // instance of Chrome or (3) a resumption of a previous Chrome journey.
        if (!HistoryManager.isAppSpecificHistoryEnabled()) return;

        boolean shouldLaunchHistory =
                IntentUtils.safeGetBooleanExtra(
                        getIntent(), IntentHandler.EXTRA_OPEN_HISTORY, false);
        if (shouldLaunchHistory) {
            HistoryManagerUtils.showHistoryManager(
                    this, getActivityTab(), getTabModelSelector().isIncognitoSelected());
        }
    }

    private boolean maybeLaunchDraggedTabInWindow(Intent intent) {
        if (!TabUiFeatureUtilities.isTabDragToCreateInstanceSupported()) return false;
        int draggedTabId =
                IntentUtils.safeGetIntExtra(
                        intent, IntentHandler.EXTRA_DRAGGED_TAB_ID, Tab.INVALID_TAB_ID);
        if (draggedTabId == Tab.INVALID_TAB_ID) return false;
        if (!IntentHandler.wasIntentSenderChrome(intent)) return false;
        if (mMultiInstanceManager == null) return false;

        // |draggedTabId| is retrieved from the activity the tab is being dragged from.
        Tab tab = TabWindowManagerSingleton.getInstance().getTabById(draggedTabId);
        if (tab == null) {
            RecordHistogram.recordBooleanHistogram(HISTOGRAM_DRAGGED_TAB_OPENED_NEW_WINDOW, false);
            return false;
        }
        mMultiInstanceManager.moveTabToWindow(this, tab, 0);
        RecordHistogram.recordBooleanHistogram(HISTOGRAM_DRAGGED_TAB_OPENED_NEW_WINDOW, true);
        return true;
    }

    @Override
    public void performPreInflationStartup() {
        super.performPreInflationStartup();

        // Android FrameMetrics allow tracking of java views and their deadline misses (frame
        // drops/janks).
        if (ChromeFeatureList.sCollectAndroidFrameTimelineMetrics.isEnabled()) {
            // We delay initialization because we have noticed a impact on started up, but this
            // metric collection isn't critical. Delaying gets us past start up and lets Chrome's
            // scheduler decide its priority.
            mJankTracker =
                    new JankTrackerImpl(
                            this, JankTrackerExperiment.JANK_TRACKER_DELAYED_START_MS.getValue());
        } else {
            mJankTracker = new PlaceholderJankTracker();
        }

        // Decide whether to record startup UMA histograms. This is done early in the main
        // Activity.onCreate() to avoid recording navigation delays when they require user input to
        // proceed. Having an uninitialized native library has been taken as a sign of starting
        // Chrome with an immediate navigation without user input.
        // TODO(crbug.com/40926074): Native library initialization was moved to another thread, and
        //  it now proceeds faster, making the metrics think that the start is not cold enough.
        //  To cover more startup cases change the heuristic detecting cold startup that happens
        //  without user interaction.
        if (!LibraryLoader.getInstance().isInitialized()) {
            setTrackColdStartupMetrics(true);
        }

        // Enable Paint Preview only on a cold start. This way the Paint preview is most useful by
        // being much faster than the real load of the page. Also cold start detection excludes user
        // interactions changing the course of restoring the page.
        if (ColdStartTracker.wasColdOnFirstActivityCreationOrNow()
                && !SimpleStartupForegroundSessionDetector.isSessionDiscarded()) {
            StartupPaintPreviewHelper.enableShowOnRestore();
        }

        supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
        IncognitoTabHostRegistry.getInstance().register(mIncognitoTabHost);
        mStartupPaintPreviewHelperSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
        mDseNewTabUrlManager = new DseNewTabUrlManager(mTabModelProfileSupplier);

        initHub();
    }

    @Override
    protected RootUiCoordinator createRootUiCoordinator() {
        return new TabbedRootUiCoordinator(
                this,
                this::onOmniboxFocusChanged,
                getShareDelegateSupplier(),
                getActivityTabProvider(),
                mTabModelProfileSupplier,
                mBookmarkModelSupplier,
                mTabBookmarkerSupplier,
                getTabModelSelectorSupplier(),
                mTabSwitcherSupplier,
                mIncognitoTabSwitcherSupplier,
                mHubManagerSupplier,
                mIntentMetadataOneshotSupplier,
                mLayoutStateProviderSupplier,
                this::getLastUserInteractionTime,
                getBrowserControlsManager(),
                getWindowAndroid(),
                getLifecycleDispatcher(),
                getLayoutManagerSupplier(),
                /* menuOrKeyboardActionController= */ this,
                this::getActivityThemeColor,
                getModalDialogManagerSupplier(),
                /* appMenuBlocker= */ this,
                this::supportsAppMenu,
                this::supportsFindInPage,
                getTabCreatorManagerSupplier(),
                getFullscreenManager(),
                getCompositorViewHolderSupplier(),
                getTabContentManagerSupplier(),
                this::getSnackbarManager,
                mEdgeToEdgeControllerSupplier,
                getActivityType(),
                this::isInOverviewMode,
                /* appMenuDelegate= */ this,
                /* statusBarColorProvider= */ this,
                new OneshotSupplierImpl<>(),
                getIntentRequestTracker(),
                getWindowAndroid().getInsetObserver(),
                this::backShouldCloseTab,
                // TODO(sinansahin): This currently only checks for incognito extras in the intent.
                // We should make it more robust by using more signals.
                IntentHandler.hasAnyIncognitoExtra(getIntent().getExtras()),
                mBackPressManager,
                getSavedInstanceState(),
                mMultiInstanceManager,
                getHubOverviewColorSupplier(),
                getBaseChromeLayout(),
                mManualFillingComponentSupplier);
    }

    @Override
    protected int getControlContainerLayoutId() {
        return R.layout.control_container;
    }

    @Override
    protected int getToolbarLayoutId() {
        return isTablet() ? R.layout.toolbar_tablet : R.layout.toolbar_phone;
    }

    @Override
    public void performPostInflationStartup() {
        super.performPostInflationStartup();

        if (isFinishing()) return;

        FontPreloader.getInstance().onPostInflationStartupTabbedActivity();

        TabModelSelector tabModelSelector = getTabModelSelector();
        IncognitoProfileDestroyer.observeTabModelSelector(tabModelSelector);
        IncognitoNotificationPresenceController.observeTabModelSelector(tabModelSelector);

        // Don't show the keyboard until user clicks in.
        getWindow()
                .setSoftInputMode(
                        WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
                                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        mContentContainer = findViewById(android.R.id.content);
        mControlContainer = findViewById(R.id.control_container);

        // Instead of overriding AsyncInitializationActivity#onFirstDrawComplete like the other
        // activities, we're adding our own draw detector here because this activity's draw can be
        // blocked by AppLaunchDrawBlocker, and #onFirstDrawComplete doesn't account for that.
        FirstDrawDetector.waitForFirstDrawStrict(
                mContentContainer, () -> FontPreloader.getInstance().onFirstDrawTabbedActivity());

        Supplier<Boolean> dialogVisibilitySupplier = null;
        dialogVisibilitySupplier =
                () -> {
                    // Return true if dialog from either tab switcher or tab strip is visible.
                    ToolbarManager toolbarManager = getToolbarManager();
                    TabGroupUi tabGroupUi = toolbarManager.getTabGroupUi();
                    boolean isDialogVisible =
                            tabGroupUi != null && tabGroupUi.isTabGridDialogVisible();

                    if (!mTabSwitcherSupplier.hasValue()) {
                        // The grid tab switcher may be lazily initialized; early out if it isn't
                        // ready.
                        return isDialogVisible;
                    }

                    Supplier<Boolean> tabSwitcherDialogVisibilitySupplier =
                            mTabSwitcherSupplier.get().getTabGridDialogVisibilitySupplier();

                    if (tabSwitcherDialogVisibilitySupplier != null) {
                        isDialogVisible |= tabSwitcherDialogVisibilitySupplier.get();
                    }
                    var incognitoTabSwitcher = mIncognitoTabSwitcherSupplier.get();
                    if (incognitoTabSwitcher != null) {
                        var incognitoDialogVisibilitySupplier =
                                incognitoTabSwitcher.getTabGridDialogVisibilitySupplier();
                        if (incognitoDialogVisibilitySupplier != null) {
                            isDialogVisible |= incognitoDialogVisibilitySupplier.get();
                        }
                    }
                    return isDialogVisible;
                };

        mUndoBarPopupController =
                new UndoBarController(
                        this,
                        mTabModelSelector,
                        this::getSnackbarManager,
                        dialogVisibilitySupplier);

        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
            TabModelUtils.runOnTabStateInitialized(
                    getTabModelSelectorSupplier().get(),
                    mCallbackController.makeCancelable(
                            (tabModelSelectorReturn) -> {
                                TabGroupColorUtils.assignTabGroupColorsIfApplicable(
                                        (TabGroupModelFilter)
                                                tabModelSelectorReturn
                                                        .getTabModelFilterProvider()
                                                        .getCurrentTabModelFilter());
                            }));
        } else {
            new BackgroundOnlyAsyncTask<Void>() {
                @Override
                protected final Void doInBackground() {
                    // Delete the tab group color SharedPreferences file on a background thread.
                    TabGroupColorUtils.clearTabGroupColorInfo();
                    return null;
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }

        mInactivityTracker =
                new ChromeInactivityTracker(
                        ChromePreferenceKeys.TABBED_ACTIVITY_LAST_BACKGROUNDED_TIME_MS_PREF);
        TabUsageTracker.initialize(this.getLifecycleDispatcher(), tabModelSelector);
        TabGroupUsageTracker.initialize(
                this.getLifecycleDispatcher(), tabModelSelector, this::isWarmOnResume);

        assert getLegacyTabStartupMetricsTracker() != null;
        assert getStartupMetricsTracker() != null;
        StartupPaintPreviewHelper paintPreviewHelper =
                new StartupPaintPreviewHelper(
                        getWindowAndroid(),
                        getOnCreateTimestampMs(),
                        getBrowserControlsManager(),
                        getTabModelSelector(),
                        () -> {
                            return getToolbarManager() == null
                                    ? null
                                    : getToolbarManager().getProgressBarCoordinator();
                        });
        mStartupPaintPreviewHelperSupplier.set(paintPreviewHelper);
        getLegacyTabStartupMetricsTracker().registerPaintPreviewObserver(paintPreviewHelper);
        getStartupMetricsTracker().registerPaintPreviewObserver(paintPreviewHelper);

        maybeRegisterHomeModules();
    }

    private void maybeRegisterHomeModules() {
        if (!HomeModulesMetricsUtils.useMagicStack()) return;

        ModuleRegistry moduleRegistry =
                new ModuleRegistry(
                        HomeModulesConfigManager.getInstance(), getLifecycleDispatcher());
        SingleTabModuleBuilder singleTabModuleBuilder =
                new SingleTabModuleBuilder(
                        this, getTabModelSelectorSupplier(), getTabContentManagerSupplier());
        moduleRegistry.registerModule(ModuleType.SINGLE_TAB, singleTabModuleBuilder);

        if (ChromeFeatureList.sPriceChangeModule.isEnabled()) {
            PriceChangeModuleBuilder priceChangeModuleBuilder =
                    new PriceChangeModuleBuilder(this, mTabModelProfileSupplier, mTabModelSelector);
            moduleRegistry.registerModule(ModuleType.PRICE_CHANGE, priceChangeModuleBuilder);
        }

        if (ChromeFeatureList.sTabResumptionModuleAndroid.isEnabled()) {
            TabResumptionModuleBuilder tabResumptionModuleBuilder =
                    new TabResumptionModuleBuilder(
                            this,
                            mTabModelProfileSupplier,
                            getTabModelSelectorSupplier(),
                            getTabContentManagerSupplier());
            moduleRegistry.registerModule(ModuleType.TAB_RESUMPTION, tabResumptionModuleBuilder);
        }

        if (ChromeFeatureList.sSafetyHubMagicStack.isEnabled()) {
            SafetyHubMagicStackBuilder safetyHubMagicStackBuilder =
                    new SafetyHubMagicStackBuilder(
                            this,
                            mTabModelProfileSupplier,
                            mTabModelSelector,
                            getModalDialogManagerSupplier());
            moduleRegistry.registerModule(ModuleType.SAFETY_HUB, safetyHubMagicStackBuilder);
        }

        if (ChromeFeatureList.sEducationalTipModule.isEnabled()) {
            EducationalTipModuleBuilder educationalTipModuleBuilder =
                    new EducationalTipModuleBuilder(this);
            moduleRegistry.registerModule(ModuleType.EDUCATIONAL_TIP, educationalTipModuleBuilder);
        }

        mModuleRegistrySupplier.set(moduleRegistry);
    }

    private boolean shouldIgnoreIntent() {
        if (mShouldIgnoreIntent == null) {
            // We call this only once to give a consistent view of whether the intent should be
            // ignored during startup as this function depends on transient state like whether the
            // screen is on.
            mShouldIgnoreIntent = IntentHandler.shouldIgnoreIntent(getIntent(), this);
        }
        return mShouldIgnoreIntent;
    }

    @Override
    protected TabModelOrchestrator createTabModelOrchestrator() {
        boolean tabMergingEnabled =
                mMultiInstanceManager != null && mMultiInstanceManager.isTabModelMergingEnabled();
        mTabModelOrchestrator =
                new TabbedModeTabModelOrchestrator(tabMergingEnabled, getLifecycleDispatcher());
        if (ChromeFeatureList.sTabStripStartupRefactoring.isEnabled()) {
            mTabModelStartupInfoSupplier = new ObservableSupplierImpl<>();
            mTabModelOrchestrator.setStartupInfoObservableSupplier(mTabModelStartupInfoSupplier);
        }
        return mTabModelOrchestrator;
    }

    @Override
    protected void createTabModels() {
        assert mTabModelSelector == null;
        assert mWindowId != INVALID_WINDOW_ID;

        Bundle savedInstanceState = getSavedInstanceState();

        // We determine the model as soon as possible so every systems get initialized coherently.
        boolean startIncognito =
                savedInstanceState != null
                        && savedInstanceState.getBoolean(IS_INCOGNITO_SELECTED, false);

        mNextTabPolicySupplier = new ChromeNextTabPolicySupplier(mLayoutStateProviderSupplier);

        boolean tabModelWasCreated =
                mTabModelOrchestrator.createTabModels(
                        this,
                        getProfileProviderSupplier(),
                        this,
                        mNextTabPolicySupplier,
                        this,
                        mWindowId);
        if (!tabModelWasCreated) {
            finishAndRemoveTask();
            return;
        }

        if (mMultiInstanceManager != null) {
            int assignedIndex = TabWindowManagerSingleton.getInstance().getIndexForWindow(this);
            // The given index and the one computed by TabWindowManager should be one and the same.
            int taskId = ApplicationStatus.getTaskId(this);
            Map<String, Integer> taskMap =
                    ChromeSharedPreferences.getInstance()
                            .readIntsWithPrefix(ChromePreferenceKeys.MULTI_INSTANCE_TASK_MAP);
            String message =
                    String.format(
                            Locale.getDefault(),
                            "Instance mismatch for assignedIndex: %d, mWindowId: %d with taskId:"
                                    + " %s and taskMap: %s",
                            assignedIndex,
                            mWindowId,
                            taskId,
                            taskMap);

            if (MultiWindowUtils.isMultiInstanceApi31Enabled()) {
                boolean indicesMatch = assignedIndex == mWindowId;
                assert indicesMatch : message;
                if (!indicesMatch) {
                    Log.i(TAG_MULTI_INSTANCE, message);
                }
            }

            mMultiInstanceManager.initialize(assignedIndex, taskId);
        }

        mTabModelSelector = mTabModelOrchestrator.getTabModelSelector();
        mTabModelSelectorObserver =
                new TabModelSelectorObserver() {
                    @Override
                    public void onTabStateInitialized() {
                        if (mMultiInstanceManager != null) {
                            mMultiInstanceManager.onTabStateInitialized();
                        }

                        if (!mCreatedTabOnStartup) return;

                        TabModel model = mTabModelSelector.getModel(false);
                        TasksUma.recordTasksUma(model);
                    }
                };
        mTabModelSelector.addObserver(mTabModelSelectorObserver);

        mTabModelSelectorTabObserver =
                new TabModelSelectorTabObserver(mTabModelSelector) {
                    @Override
                    public void onDidFinishNavigationInPrimaryMainFrame(
                            Tab tab, NavigationHandle navigation) {
                        if (!navigation.hasCommitted()) return;

                        // Show the sync error message even if the navigation happened on incognito.
                        Profile profile = mTabModelProfileSupplier.get().getOriginalProfile();

                        try (TraceEvent e =
                                TraceEvent.scoped("CheckSyncErrorOnDidFinishNavigation")) {
                            SyncErrorMessage.maybeShowMessageUi(getWindowAndroid(), profile);
                        }
                        try (TraceEvent te = TraceEvent.scoped("updateActiveWebContents")) {
                            SendTabToSelfAndroidBridge.updateActiveWebContents(
                                    tab.getWebContents());
                        }
                    }
                };
        if (startIncognito) mTabModelSelector.selectModel(true);
    }

    TabModelSelectorObserver getTabModelSelectorObserverForTesting() {
        return mTabModelSelectorObserver;
    }

    boolean getCreatedTabOnStartupForTesting() {
        return mCreatedTabOnStartup;
    }

    void setCreatedTabOnStartupForTesting(boolean createdTabOnStartup) {
        mCreatedTabOnStartup = createdTabOnStartup;
    }

    @Override
    protected LaunchCauseMetrics createLaunchCauseMetrics() {
        return new TabbedActivityLaunchCauseMetrics(this);
    }

    @Override
    public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
        return new TabbedAppMenuPropertiesDelegate(
                this,
                getActivityTabProvider(),
                getMultiWindowModeStateDispatcher(),
                getTabModelSelector(),
                getToolbarManager(),
                getWindow().getDecorView(),
                this,
                mLayoutStateProviderSupplier,
                mBookmarkModelSupplier,
                () ->
                        getTabCreator(/* incognito= */ false)
                                .launchUrl(
                                        NewTabPageUtils.encodeNtpUrl(
                                                NewTabPageLaunchOrigin.WEB_FEED),
                                        TabLaunchType.FROM_CHROME_UI),
                getModalDialogManager(),
                getSnackbarManager(),
                mRootUiCoordinator.getIncognitoReauthControllerSupplier(),
                mRootUiCoordinator.getReadAloudControllerSupplier());
    }

    private TabDelegateFactory getTabDelegateFactory() {
        if (mTabDelegateFactory == null) {
            mTabDelegateFactory =
                    new TabbedModeTabDelegateFactory(
                            this,
                            getAppBrowserControlsVisibilityDelegate(),
                            getShareDelegateSupplier(),
                            mRootUiCoordinator.getEphemeralTabCoordinatorSupplier(),
                            ((TabbedRootUiCoordinator) mRootUiCoordinator)::onContextMenuCopyLink,
                            mRootUiCoordinator.getBottomSheetController(),
                            /* chromeActivityNativeDelegate= */ this,
                            /* isCustomTab= */ false,
                            getBrowserControlsManager(),
                            getFullscreenManager(),
                            /* tabCreatorManager= */ this,
                            getTabModelSelectorSupplier(),
                            getCompositorViewHolderSupplier(),
                            getModalDialogManagerSupplier(),
                            this::getSnackbarManager,
                            getBrowserControlsManager(),
                            getActivityTabProvider(),
                            getLifecycleDispatcher(),
                            getWindowAndroid(),
                            mJankTracker,
                            getToolbarManager()::getToolbar,
                            mHomeSurfaceTracker,
                            getTabContentManagerSupplier(),
                            getToolbarManager().getTabStripHeightSupplier(),
                            mModuleRegistrySupplier);
        }
        return mTabDelegateFactory;
    }

    @Override
    protected Pair<ChromeTabCreator, ChromeTabCreator> createTabCreators() {
        return Pair.create(
                new ChromeTabCreator(
                        this,
                        getWindowAndroid(),
                        this::getTabDelegateFactory,
                        getProfileProviderSupplier(),
                        false,
                        AsyncTabParamsManagerSingleton.getInstance(),
                        getTabModelSelectorSupplier(),
                        getCompositorViewHolderSupplier(),
                        DseNewTabUrlManager.isSwapOutNtpFlagEnabled()
                                ? mDseNewTabUrlManager
                                : null),
                new ChromeTabCreator(
                        this,
                        getWindowAndroid(),
                        this::getTabDelegateFactory,
                        getProfileProviderSupplier(),
                        true,
                        AsyncTabParamsManagerSingleton.getInstance(),
                        getTabModelSelectorSupplier(),
                        getCompositorViewHolderSupplier(),
                        null));
    }

    @Override
    protected void initDeferredStartupForActivity() {
        super.initDeferredStartupForActivity();
        DeferredStartupHandler.getInstance().addDeferredTask(this::onDeferredStartup);
    }

    private void onDeferredStartup() {
        if (isActivityFinishingOrDestroyed()) {
            return;
        }

        LauncherShortcutActivity.updateIncognitoShortcut(
                ChromeTabbedActivity.this, mTabModelProfileSupplier.get());

        ChromeSurveyController.initialize(
                mTabModelSelector,
                getLifecycleDispatcher(),
                ChromeTabbedActivity.this,
                MessageDispatcherProvider.from(getWindowAndroid()),
                mTabModelProfileSupplier.get());
    }

    @Override
    protected void recordIntentToCreationTime(long timeMs) {
        super.recordIntentToCreationTime(timeMs);
        RecordHistogram.recordCustomTimesHistogram(
                "MobileStartup.IntentToCreationTime.TabbedMode",
                timeMs,
                1,
                DateUtils.SECOND_IN_MILLIS * 30,
                50);
    }

    @Override
    protected boolean isStartedUpCorrectly(Intent intent) {
        mWindowId = 0;
        mInstanceAllocationType = InstanceAllocationType.DEFAULT;
        Bundle savedInstanceState = getSavedInstanceState();
        int windowId = getExtraWindowIdFromIntent(intent);
        if (savedInstanceState != null && savedInstanceState.containsKey(WINDOW_INDEX)) {
            // Activity is recreated after destruction. |windowId| must not be valid in this case.
            assert windowId == INVALID_WINDOW_ID;
            Log.i(TAG_MULTI_INSTANCE, "Retrieved windowId from saved instance state.");
            mWindowId = savedInstanceState.getInt(WINDOW_INDEX, 0);
        } else if (mMultiInstanceManager != null) {
            // |allocInstanceId| doesn't do any disk I/O that would add a long-running task
            // to pre-inflation startup.
            boolean preferNew = getExtraPreferNewFromIntent(intent);
            Pair<Integer, Integer> instanceIdInfo =
                    mMultiInstanceManager.allocInstanceId(
                            windowId, ApplicationStatus.getTaskId(this), preferNew);
            mWindowId = instanceIdInfo.first;
            mInstanceAllocationType = instanceIdInfo.second;
            logIntentInfo(intent);
            // If a new instance ID was allocated for the newly created activity, potentially
            // dispatch it to an existing activity under special circumstances. See
            // |#maybeDispatchIntentInExistingActivity(Intent)| for details.
            if (instanceIdInfo.second == InstanceAllocationType.NEW_INSTANCE_NEW_TASK
                    && maybeDispatchIntentInExistingActivity(intent)) {
                return false;
            }
        }
        if (mWindowId == INVALID_WINDOW_ID) {
            Log.i(TAG, "Window ID not allocated. Finishing the activity");
            Toast.makeText(this, R.string.max_number_of_windows, Toast.LENGTH_LONG).show();
            recordMaxWindowLimitExceededHistogram(/* limitExceeded= */ true);
            return false;
        } else {
            Map<String, Integer> taskMap =
                    ChromeSharedPreferences.getInstance()
                            .readIntsWithPrefix(ChromePreferenceKeys.MULTI_INSTANCE_TASK_MAP);
            Log.i(
                    TAG_MULTI_INSTANCE,
                    "Window ID allocated: " + mWindowId + ", instance-task map: " + taskMap);
        }

        if (mMultiInstanceManager != null
                && !mMultiInstanceManager.isStartedUpCorrectly(ApplicationStatus.getTaskId(this))) {
            return false;
        }
        recordMaxWindowLimitExceededHistogram(/* limitExceeded= */ false);

        return super.isStartedUpCorrectly(intent);
    }

    private void logIntentInfo(Intent intent) {
        var logMessage =
                "Intent routed via ChromeLauncherActivity: "
                        + IntentUtils.safeGetBooleanExtra(
                                intent,
                                IntentHandler.EXTRA_LAUNCHED_VIA_CHROME_LAUNCHER_ACTIVITY,
                                false)
                        + "\nActivity referrer: "
                        + getReferrer()
                        + "\nIntent referrer extra: "
                        + IntentUtils.safeGetStringExtra(
                                intent, IntentHandler.EXTRA_ACTIVITY_REFERRER)
                        + "\nIntent contains LAUNCHER category: "
                        + intent.hasCategory(Intent.CATEGORY_LAUNCHER)
                        + "\nIntent contains FLAG_ACTIVITY_MULTIPLE_TASK: "
                        + ((intent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0)
                        + "\nIntent contains FLAG_ACTIVITY_NEW_TASK: "
                        + ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
                        + "\nIntent component: "
                        + (intent.getComponent() == null
                                ? "N/A"
                                : intent.getComponent().getClassName())
                        + "\nIntent hash: "
                        + System.identityHashCode(intent);
        Log.i(TAG_MULTI_INSTANCE, logMessage);
    }

    // It is possible that an undesired attempt is made to launch a VIEW intent in a new
    // ChromeTabbedActivity by an external app. When a new activity is created using such an intent,
    // we will try to launch it in an existing ChromeTabbedActivity instead, and finish the newly
    // created ChromeTabbedActivity.
    private boolean maybeDispatchIntentInExistingActivity(Intent intent) {
        if (!MultiWindowUtils.isMultiInstanceApi31Enabled()) {
            return false;
        }

        if (!ChromeFeatureList.sRedirectExplicitCTAIntentsToExistingActivity.isEnabled()) {
            return false;
        }

        // Check if the intent creating a new ChromeTabbedActivity was a VIEW intent launched via
        // ChromeLauncherActivity, in which case continue to launch in the current activity.
        boolean isExplicitViewIntent =
                Intent.ACTION_VIEW.equals(intent.getAction())
                        && !IntentUtils.safeGetBooleanExtra(
                                intent,
                                IntentHandler.EXTRA_LAUNCHED_VIA_CHROME_LAUNCHER_ACTIVITY,
                                false);
        if (!isExplicitViewIntent) return false;

        // If the intent sender is Chrome, continue to launch in the current activity.
        if (IntentHandler.wasIntentSenderChrome(intent)) {
            return false;
        }

        // Find an instance to launch the intent in.
        int instanceId = MultiWindowUtils.getInstanceIdForViewIntent(false);

        // If there is no running ChromeTabbedActivity, continue to launch in the current activity.
        if (instanceId == INVALID_WINDOW_ID) {
            RecordHistogram.recordBooleanHistogram(
                    HISTOGRAM_EXPLICIT_VIEW_INTENT_FINISHED_NEW_ACTIVITY, false);
            return false;
        }

        intent.putExtra(IntentHandler.EXTRA_WINDOW_ID, instanceId);
        MultiWindowUtils.launchIntentInInstance(intent, instanceId);
        RecordHistogram.recordBooleanHistogram(
                HISTOGRAM_EXPLICIT_VIEW_INTENT_FINISHED_NEW_ACTIVITY, true);
        return true;
    }

    private void recordMaxWindowLimitExceededHistogram(boolean limitExceeded) {
        RecordHistogram.recordBooleanHistogram(
                "Android.MultiInstance.MaxWindowLimitExceeded", limitExceeded);
    }

    private static int getExtraWindowIdFromIntent(Intent intent) {
        int windowId =
                IntentUtils.safeGetIntExtra(
                        intent, IntentHandler.EXTRA_WINDOW_ID, INVALID_WINDOW_ID);
        return IntentUtils.isTrustedIntentFromSelf(intent) ? windowId : INVALID_WINDOW_ID;
    }

    private static boolean getExtraPreferNewFromIntent(Intent intent) {
        return IntentUtils.safeGetBooleanExtra(intent, IntentHandler.EXTRA_PREFER_NEW, false);
    }

    @Override
    public void terminateIncognitoSession() {
        getTabModelSelector().getModel(true).closeTabs(TabClosureParams.closeAllTabs().build());
    }

    @Override
    public boolean onMenuOrKeyboardAction(final int id, boolean fromMenu) {
        final Tab currentTab = getActivityTab();
        boolean currentTabIsNtp = isTabNtp(currentTab);
        if (id == R.id.new_tab_menu_id) {
            if (!mTabModelSelector.isTabStateInitialized()) return false;

            getTabModelSelector().getModel(false).commitAllTabClosures();
            RecordUserAction.record("MobileMenuNewTab");
            RecordUserAction.record("MobileNewTabOpened");
            reportNewTabShortcutUsed(false);
            if (fromMenu) RecordUserAction.record("MobileMenuNewTab.AppMenu");

            getTabCreator(false).launchNtp();

            mLocaleManager.showSearchEnginePromoIfNeeded(this, null);
        } else if (id == R.id.new_incognito_tab_menu_id) {
            if (!mTabModelSelector.isTabStateInitialized()) return false;

            Profile profile = mTabModelSelector.getCurrentModel().getProfile();
            if (IncognitoUtils.isIncognitoModeEnabled(profile)) {
                getTabModelSelector().getModel(false).commitAllTabClosures();
                // This action must be recorded before opening the incognito tab since UMA actions
                // are dropped when an incognito tab is open.
                RecordUserAction.record("MobileMenuNewIncognitoTab");
                RecordUserAction.record("MobileNewTabOpened");
                reportNewTabShortcutUsed(true);
                if (fromMenu) RecordUserAction.record("MobileMenuNewIncognitoTab.AppMenu");
                getTabCreator(true).launchNtp();
                Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
                tracker.notifyEvent(EventConstants.APP_MENU_NEW_INCOGNITO_TAB_CLICKED);
            }
        } else if (id == R.id.all_bookmarks_menu_id) {
            getCompositorViewHolderSupplier()
                    .get()
                    .hideKeyboard(
                            () -> {
                                BookmarkUtils.showBookmarkManager(
                                        ChromeTabbedActivity.this,
                                        getCurrentTabModel().isIncognito());
                            });
            if (currentTabIsNtp) {
                NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_BOOKMARKS_MANAGER);
            }

            @BrowserProfileType
            int type =
                    getCurrentTabModel().isIncognito()
                            ? BrowserProfileType.INCOGNITO
                            : BrowserProfileType.REGULAR;
            RecordHistogram.recordEnumeratedHistogram(
                    "Bookmarks.OpenBookmarkManager.PerProfileType",
                    type,
                    BrowserProfileType.MAX_VALUE + 1);

            RecordUserAction.record("MobileMenuAllBookmarks");
        } else if (id == R.id.recent_tabs_menu_id) {
            LoadUrlParams params =
                    new LoadUrlParams(UrlConstants.RECENT_TABS_URL, PageTransition.AUTO_BOOKMARK);
            boolean isInOverviewMode = isInOverviewMode();
            if (currentTab != null) {
                currentTab.loadUrl(params);
            } else {
                getTabCreator(getCurrentTabModel().isIncognito())
                        .createNewTab(params, TabLaunchType.FROM_CHROME_UI, null);
            }
            if (isInOverviewMode) {
                mLayoutManager.showLayout(LayoutType.BROWSING, true);
            }

            if (currentTabIsNtp) {
                NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_RECENT_TABS_MANAGER);
            }
            RecordUserAction.record("MobileMenuRecentTabs");
        } else if (id == R.id.close_tab) {
            getCurrentTabModel().closeTabs(TabClosureParams.closeTab(currentTab).build());
            RecordUserAction.record("MobileTabClosed");
        } else if (id == R.id.close_all_tabs_menu_id) {
            // Close both incognito and normal tabs.
            Runnable closeAllTabsRunnable =
                    CloseAllTabsHelper.buildCloseAllTabsRunnable(
                            mHubProvider.getHubManagerSupplier(),
                            mTabSwitcherSupplier,
                            mIncognitoTabSwitcherSupplier,
                            getTabModelSelectorSupplier().get(),
                            /* isIncognitoOnly= */ false);
            CloseAllTabsDialog.show(
                    this,
                    getModalDialogManagerSupplier(),
                    getTabModelSelectorSupplier().get(),
                    closeAllTabsRunnable);
            RecordUserAction.record("MobileMenuCloseAllTabs");
        } else if (id == R.id.close_all_incognito_tabs_menu_id) {
            // Close only incognito tabs
            Runnable closeAllTabsRunnable =
                    CloseAllTabsHelper.buildCloseAllTabsRunnable(
                            mHubProvider.getHubManagerSupplier(),
                            mTabSwitcherSupplier,
                            mIncognitoTabSwitcherSupplier,
                            getTabModelSelectorSupplier().get(),
                            /* isIncognitoOnly= */ true);
            CloseAllTabsDialog.show(
                    this,
                    getModalDialogManagerSupplier(),
                    getTabModelSelectorSupplier().get(),
                    closeAllTabsRunnable);
            RecordUserAction.record("MobileMenuCloseAllIncognitoTabs");
        } else if (id == R.id.focus_url_bar) {
            boolean isUrlBarVisible =
                    !isInOverviewMode() && (!isTablet() || getCurrentTabModel().getCount() != 0);
            if (isUrlBarVisible) {
                getToolbarManager()
                        .setUrlBarFocus(true, OmniboxFocusReason.MENU_OR_KEYBOARD_ACTION);
            }
        } else if (id == R.id.downloads_menu_id) {
            OTRProfileID otrProfileID = null;
            if (currentTab != null) {
                otrProfileID = currentTab.getProfile().getOTRProfileID();
            }
            DownloadUtils.showDownloadManager(
                    this, currentTab, otrProfileID, DownloadOpenSource.MENU);
            if (currentTabIsNtp) {
                NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_DOWNLOADS_MANAGER);
            }
            RecordUserAction.record("MobileMenuDownloadManager");
        } else if (id == R.id.open_recently_closed_tab) {
            TabModel currentModel = mTabModelSelector.getCurrentModel();
            if (!currentModel.isIncognito()) currentModel.openMostRecentlyClosedEntry();
            RecordUserAction.record("MobileTabClosedUndoShortCut");
        } else if (id == R.id.quick_delete_menu_id
                && QuickDeleteController.isQuickDeleteEnabled()) {
            assert !mTabModelSelector.getCurrentModel().isIncognito()
                    : "Quick delete is not supported in Incognito.";

            if (getLayoutManager().getActiveLayoutType() == LayoutType.TAB_SWITCHER) {
                QuickDeleteMetricsDelegate.recordHistogram(
                        QuickDeleteMetricsDelegate.QuickDeleteAction
                                .TAB_SWITCHER_MENU_ITEM_CLICKED);
            } else {
                QuickDeleteMetricsDelegate.recordHistogram(
                        QuickDeleteMetricsDelegate.QuickDeleteAction.MENU_ITEM_CLICKED);
            }

            new QuickDeleteController(
                    this,
                    new QuickDeleteDelegateImpl(mTabModelProfileSupplier, mTabSwitcherSupplier),
                    getModalDialogManager(),
                    getSnackbarManager(),
                    getLayoutManager(),
                    mTabModelSelector,
                    ArchivedTabModelOrchestrator.getForProfile(mTabModelProfileSupplier.get())
                            .getTabModelSelector());
        } else if (id == R.id.switch_to_incognito_menu_id) {
            mTabModelSelector.selectModel(true);
            RecordUserAction.record("MobileMenuSwitchToIncognito");
        } else if (id == R.id.switch_out_of_incognito_menu_id) {
            mTabModelSelector.selectModel(false);
            RecordUserAction.record("MobileMenuSwitchOutOfIncognito");
        } else {
            return super.onMenuOrKeyboardAction(id, fromMenu);
        }
        return true;
    }

    private boolean isTabNtp(Tab tab) {
        return tab != null && UrlUtilities.isNtpUrl(tab.getUrl());
    }

    private boolean isTabRegularNtp(Tab tab) {
        return isTabNtp(tab) && !tab.isIncognito();
    }

    private void onOmniboxFocusChanged(boolean hasFocus) {
        getTabModalLifetimeHandler().onOmniboxFocusChanged(hasFocus);
    }

    private void recordLauncherShortcutAction(boolean isIncognito) {
        if (isIncognito) {
            RecordUserAction.record("Android.LauncherShortcut.NewIncognitoTab");
        } else {
            RecordUserAction.record("Android.LauncherShortcut.NewTab");
        }
    }

    @Override
    public boolean handleBackPressed() {
        // Back press event should be handled through the back press handler.
        assert !BackPressManager.isEnabled() : "Incorrect way of handling back press.";
        if (!mUIWithNativeInitialized) {
            RecordUserAction.record("SystemBackBeforeUINativeInitialized");
            return false;
        }

        if (getToolbarManager() != null && getToolbarManager().unfocusUrlBarOnBackPress()) {
            BackPressManager.record(BackPressHandler.Type.LOCATION_BAR);
            return true;
        }

        if (getTabModalLifetimeHandler().onBackPressed()) {
            BackPressManager.record(BackPressHandler.Type.TAB_MODAL_HANDLER);
            return true;
        }

        final Tab activityTab =
                BackPressManager.shouldUseActivityTabProvider()
                        ? getActivityTabProvider().get()
                        : getActivityTab();
        final Tab currentTab = activityTab;

        if (currentTab == null) {
            minimizeAppAndCloseTabOnBackPress(null);
            return true;
        }

        final WebContents webContents = currentTab.getWebContents();
        if (webContents != null) {
            RenderFrameHost focusedFrame = webContents.getFocusedFrame();
            if (focusedFrame != null && focusedFrame.signalCloseWatcherIfActive()) {
                BackPressManager.record(BackPressHandler.Type.CLOSE_WATCHER);
                return true;
            }
        }

        if (getToolbarManager().back()) {
            BackPressManager.record(BackPressHandler.Type.TAB_HISTORY);
            return true;
        }

        final @TabLaunchType int type = currentTab.getLaunchType();

        if (type == TabLaunchType.FROM_READING_LIST) {
            assert !isTablet() : "Not expecting to see FROM_READING_LIST on tablets";
            assert mReadingListBackPressHandler != null;
            mReadingListBackPressHandler.handleBackPress();
            BackPressManager.record(BackPressHandler.Type.SHOW_READING_LIST);
            return true;
        }

        // At this point we know either the tab will close or the app will minimize.
        NativePage nativePage = currentTab.getNativePage();
        if (nativePage != null) {
            nativePage.notifyHidingWithBack();
        }

        if (minimizeAppAndCloseTabOnBackPress(currentTab)) return true;

        assert false : "The back button should have already been handled by this point";
        return false;
    }

    private boolean minimizeAppAndCloseTabOnBackPress(@Nullable Tab currentTab) {
        if (currentTab == null) {
            BackPressManager.record(BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB);
            MinimizeAppAndCloseTabBackPressHandler.record(MinimizeAppAndCloseTabType.MINIMIZE_APP);
            assertOnLastBackPress();
            moveTaskToBack(true);
            return true;
        }
        // TAB history handler has a higher priority and should navigate page back before
        // minimizing app and closing tab.
        assert !currentTab.canGoBack()
                : "Tab should be navigated back before closing or exiting app";
        final boolean shouldCloseTab = backShouldCloseTab(currentTab);
        final WebContents webContents = currentTab.getWebContents();

        // Minimize the app if either:
        // - we decided not to close the tab
        // - we decided to close the tab, but it was opened by an external app, so we will go
        //   exit Chrome on top of closing the tab
        final boolean minimizeApp =
                !shouldCloseTab || TabAssociatedApp.isOpenedFromExternalApp(currentTab);

        BackPressManager.record(BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB);
        assertOnLastBackPress();

        if (minimizeApp) {
            if (shouldCloseTab) {
                MinimizeAppAndCloseTabBackPressHandler.record(
                        MinimizeAppAndCloseTabType.MINIMIZE_APP_AND_CLOSE_TAB);
                sendToBackground(currentTab);
            } else {
                MinimizeAppAndCloseTabBackPressHandler.record(
                        MinimizeAppAndCloseTabType.MINIMIZE_APP);
                sendToBackground(null);
            }
            return true;
        } else if (shouldCloseTab) {
            MinimizeAppAndCloseTabBackPressHandler.record(MinimizeAppAndCloseTabType.CLOSE_TAB);
            if (webContents != null) webContents.dispatchBeforeUnload(false);
            return true;
        }
        return false;
    }

    private void assertOnLastBackPress() {
        final Tab currentTab = getActivityTab();
        var activityTab = getActivityTabProvider().get();
        MinimizeAppAndCloseTabBackPressHandler.assertOnLastBackPress(
                currentTab,
                activityTab,
                this::backShouldCloseTab,
                mLayoutStateProviderSupplier,
                isActivityFinishingOrDestroyed());
    }

    private void initializeBackPressHandlers() {
        // Initialize some back press handlers early to reduce code duplication.
        mReadingListBackPressHandler =
                new ReadingListBackPressHandler(getActivityTabProvider(), mBookmarkModelSupplier);

        if (!BackPressManager.isEnabled()) {
            return;
        }

        mBackPressManager.setHasSystemBackArm(true);
        if (!isTablet()) {
            mBackPressManager.addHandler(
                    mReadingListBackPressHandler, BackPressHandler.Type.SHOW_READING_LIST);
        }
        if (mMinimizeAppAndCloseTabBackPressHandler == null) {
            mMinimizeAppAndCloseTabBackPressHandler =
                    new MinimizeAppAndCloseTabBackPressHandler(
                            getActivityTabProvider(),
                            this::backShouldCloseTab,
                            this::sendToBackground,
                            this::assertOnLastBackPress,
                            getLayoutStateProviderSupplier());
            mBackPressManager.addHandler(
                    mMinimizeAppAndCloseTabBackPressHandler,
                    BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB);
        }
    }

    /**
     * [true]: Reached the bottom of the back stack on a tab the user did not explicitly
     * create (i.e. it was created by an external app or opening a link in background, etc).
     * [false]: Reached the bottom of the back stack on a tab that the user explicitly
     * created (e.g. selecting "new tab" from menu).
     *
     * @return Whether pressing the back button on the provided Tab should close the Tab.
     */
    @Override
    public boolean backShouldCloseTab(Tab tab) {
        if (!tab.isInitialized() || tab.isClosing() || tab.isDestroyed()) {
            return false;
        }
        @TabLaunchType int type = tab.getLaunchType();

        return type == TabLaunchType.FROM_LINK
                || type == TabLaunchType.FROM_EXTERNAL_APP
                || type == TabLaunchType.FROM_READING_LIST
                || type == TabLaunchType.FROM_LONGPRESS_FOREGROUND
                || type == TabLaunchType.FROM_LONGPRESS_INCOGNITO
                || type == TabLaunchType.FROM_LONGPRESS_BACKGROUND
                || type == TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP
                || type == TabLaunchType.FROM_RECENT_TABS
                || type == TabLaunchType.FROM_RECENT_TABS_FOREGROUND
                || (type == TabLaunchType.FROM_RESTORE && tab.getParentId() != Tab.INVALID_TAB_ID);
    }

    /**
     * Sends this Activity to the background.
     *
     * @param tabToClose Tab that will be closed once the app is not visible.
     */
    private void sendToBackground(@Nullable final Tab tabToClose) {
        Log.i(TAG, "sendToBackground(): " + tabToClose);
        moveTaskToBack(true);
        if (tabToClose != null) {
            // In the case of closing a tab upon minimization, don't allow the close action to
            // happen until after our app is minimized to make sure we don't get a brief glimpse of
            // the newly active tab before we exit Chrome.
            //
            // If the runnable doesn't run before the Activity dies, Chrome won't crash but the tab
            // won't be closed (crbug.com/587565).
            mHandler.postDelayed(
                    () -> {
                        if (mTabModelSelector == null
                                || tabToClose.isClosing()
                                || tabToClose.isDestroyed()) {
                            return;
                        }

                        final TabModel currentModel = mTabModelSelector.getCurrentModel();
                        final TabModel tabToCloseModel =
                                mTabModelSelector.getModel(tabToClose.isIncognito());
                        if (currentModel != tabToCloseModel) {
                            // This seems improbable; however, crbug/1463397 suggests otherwise. If
                            // this happens, remain on the current tab and close the tab in the
                            // other model.
                            tabToCloseModel.closeTabs(
                                    TabClosureParams.closeTab(tabToClose)
                                            .uponExit(true)
                                            .allowUndo(false)
                                            .build());
                            return;
                        }

                        Tab nextTab =
                                currentModel.getNextTabIfClosed(
                                        tabToClose.getId(), /* uponExit= */ true);
                        tabToCloseModel.closeTabs(
                                TabClosureParams.closeTab(tabToClose)
                                        .recommendedNextTab(nextTab)
                                        .uponExit(true)
                                        .allowUndo(false)
                                        .build());
                    },
                    CLOSE_TAB_ON_MINIMIZE_DELAY_MS);
        }
    }

    @Override
    public boolean moveTaskToBack(boolean nonRoot) {
        try {
            return super.moveTaskToBack(nonRoot);
        } catch (NullPointerException e) {
            // Work around framework bug described in https://crbug.com/817567.
            finish();
            return true;
        }
    }

    /**
     * Launch a URL from an intent.
     * @param loadUrlParams Parameters specifying the url to load.
     * @param externalAppId External app id.
     * @param forceNewTab   Whether to force the URL to be launched in a new tab or to fall
     *                      back to the default behavior for making that determination.
     * @param intent        The original intent.
     */
    private Tab launchIntent(
            LoadUrlParams loadUrlParams, String externalAppId, boolean forceNewTab, Intent intent) {
        if (mUIWithNativeInitialized && !UrlUtilities.isNtpUrl(loadUrlParams.getUrl())) {
            getLayoutManager().showLayout(LayoutType.BROWSING, false);
            getToolbarManager().finishAnimations();
        }
        if (IntentHandler.wasIntentSenderChrome(intent)) {
            // If the intent was launched by chrome, open the new tab in the appropriate model.
            boolean isIncognito =
                    IntentUtils.safeGetBooleanExtra(
                            intent, IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false);
            @TabLaunchType Integer launchType = IntentHandler.getTabLaunchType(intent);
            if (launchType == null) {
                if (IntentUtils.safeGetBooleanExtra(
                        intent, IntentHandler.EXTRA_INVOKED_FROM_SHORTCUT, false)) {
                    launchType = TabLaunchType.FROM_LAUNCHER_SHORTCUT;
                } else if (IntentUtils.safeGetBooleanExtra(
                        intent, IntentHandler.EXTRA_INVOKED_FROM_APP_WIDGET, false)) {
                    launchType = TabLaunchType.FROM_APP_WIDGET;
                } else if (IncognitoTabLauncher.didCreateIntent(intent)) {
                    launchType = TabLaunchType.FROM_LAUNCH_NEW_INCOGNITO_TAB;
                } else {
                    // Using FROM_LINK ensures the tab is parented to the current tab, which allows
                    // the back button to close these tabs and restore selection to the previous
                    // tab.
                    launchType = TabLaunchType.FROM_LINK;
                }
            }

            ChromeTabCreator tabCreator = getTabCreator(isIncognito);
            Tab firstTab = tabCreator.createNewTab(loadUrlParams, launchType, null, intent);

            List<String> additionalUrls =
                    IntentUtils.safeGetSerializableExtra(
                            intent, IntentHandler.EXTRA_ADDITIONAL_URLS);
            boolean openAdditionalUrlsInTabGroup =
                    IntentUtils.safeGetBooleanExtra(
                            intent, IntentHandler.EXTRA_OPEN_ADDITIONAL_URLS_IN_TAB_GROUP, false);
            if (additionalUrls != null) {
                final Tab parent = openAdditionalUrlsInTabGroup ? firstTab : null;
                @TabLaunchType
                int additionalUrlLaunchType =
                        openAdditionalUrlsInTabGroup
                                ? TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP
                                : TabLaunchType.FROM_RESTORE;
                for (int i = 0; i < additionalUrls.size(); i++) {
                    String url = additionalUrls.get(i);
                    LoadUrlParams copy = LoadUrlParams.copy(loadUrlParams);
                    copy.setUrl(url);
                    tabCreator.createNewTab(copy, additionalUrlLaunchType, parent);
                }
            }
            return firstTab;
        }

        // Check if the tab is being created from a Reader Mode navigation.
        if (ReaderModeManager.isEnabled() && ReaderModeManager.isReaderModeCreatedIntent(intent)) {
            Bundle extras = intent.getExtras();
            int readerParentId =
                    IntentUtils.safeGetInt(
                            extras, ReaderModeManager.EXTRA_READER_MODE_PARENT, Tab.INVALID_TAB_ID);
            extras.remove(ReaderModeManager.EXTRA_READER_MODE_PARENT);
            // Set the parent tab to the tab that Reader Mode started from.
            if (readerParentId != Tab.INVALID_TAB_ID && mTabModelSelector != null) {
                return getCurrentTabCreator()
                        .createNewTab(
                                new LoadUrlParams(loadUrlParams.getUrl(), PageTransition.LINK),
                                TabLaunchType.FROM_LINK,
                                mTabModelSelector.getTabById(readerParentId));
            }
        }

        return getTabCreator(false)
                .launchUrlFromExternalApp(loadUrlParams, externalAppId, forceNewTab, intent);
    }

    private void showOverview() {
        if (mLayoutManager == null) return;

        if (isInOverviewMode()) {
            if (didFinishNativeInitialization()) {
                getCompositorViewHolderSupplier().get().hideKeyboard(() -> {});
            }
        }

        Tab currentTab = getActivityTab();
        @LayoutType int layoutTypeToShow = LayoutType.TAB_SWITCHER;

        // If we don't have a current tab, show the overview mode.
        if (currentTab == null) {
            mLayoutManager.showLayout(layoutTypeToShow, false);
        } else {
            getCompositorViewHolderSupplier()
                    .get()
                    .hideKeyboard(() -> mLayoutManager.showLayout(layoutTypeToShow, true));
        }
    }

    private void hideOverview() {
        assert (isInOverviewMode());
        if (getCurrentTabModel().getCount() != 0) {
            // Don't hide overview if current tab stack is empty()
            mLayoutManager.showLayout(LayoutType.BROWSING, false);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onSaveInstanceState")) {
            super.onSaveInstanceState(outState);
            CipherFactory.getInstance().saveToBundle(outState);
            outState.putInt(
                    WINDOW_INDEX, TabWindowManagerSingleton.getInstance().getIndexForWindow(this));
            Boolean is_incognito = getCurrentTabModel().isIncognito();
            outState.putBoolean(IS_INCOGNITO_SELECTED, is_incognito);
            // If it's Incognito and native is initialized and profile exists, serialize duration
            // service state.
            if (is_incognito && ProfileManager.isInitialized()) {
                AndroidSessionDurationsServiceState.serializeFromNative(
                        outState, getCurrentTabModel().getProfile());
            }
        }
    }

    @Override
    public void onDestroyInternal() {
        if (mReadingListBackPressHandler != null) {
            mReadingListBackPressHandler.destroy();
            mReadingListBackPressHandler = null;
        }
        if (mMinimizeAppAndCloseTabBackPressHandler != null) {
            mMinimizeAppAndCloseTabBackPressHandler.destroy();
            mMinimizeAppAndCloseTabBackPressHandler = null;
        }

        if (mCallbackController != null) {
            mCallbackController.destroy();
            mCallbackController = null;
        }

        if (mJankTracker != null) {
            mJankTracker.destroy();
            mJankTracker = null;
        }

        if (mTabModelSelectorTabObserver != null) {
            mTabModelSelectorTabObserver.destroy();
            mTabModelSelectorTabObserver = null;
        }

        if (mHistoricalTabModelObserver != null) mHistoricalTabModelObserver.destroy();

        if (mUndoRefocusHelper != null) mUndoRefocusHelper.destroy();

        if (mTabModelObserver != null) mTabModelObserver.destroy();

        if (mTabModelSelectorObserver != null && mTabModelSelector != null) {
            mTabModelSelector.removeObserver(mTabModelSelectorObserver);
            mTabModelSelectorObserver = null;
        }

        if (mUndoBarPopupController != null) {
            mUndoBarPopupController.destroy();
            mUndoBarPopupController = null;
        }

        if (mStartupPaintPreviewHelperSupplier != null) {
            mStartupPaintPreviewHelperSupplier.destroy();
        }

        if (mModuleRegistrySupplier.hasValue()) {
            mModuleRegistrySupplier.get().destroy();
        }

        IncognitoTabHostRegistry.getInstance().unregister(mIncognitoTabHost);

        TabObscuringHandler tabObscuringHandler = getTabObscuringHandler();
        if (tabObscuringHandler != null) {
            getTabObscuringHandler().removeObserver(mCompositorViewHolder);
        }

        if (isTablet()) ChromeAccessibilityUtil.get().removeObserver(mCompositorViewHolder);
        ChromeAccessibilityUtil.get().removeObserver(mLayoutManager);

        if (mTabDelegateFactory != null) mTabDelegateFactory.destroy();

        mAppLaunchDrawBlocker.destroy();

        if (mAuxiliarySearchController != null) {
            mAuxiliarySearchController.destroy();
        }

        if (mDragDropDelegate != null) {
            mDragDropDelegate.destroy();
        }

        if (mHubProvider != null) mHubProvider.destroy();

        if (mCleanUpHubOverviewColorObserver != null) {
            mCleanUpHubOverviewColorObserver.run();
            mCleanUpHubOverviewColorObserver = null;
        }

        if (mTabGroupVisualDataManager != null) {
            mTabGroupVisualDataManager.destroy();
            mTabGroupVisualDataManager = null;
        }

        if (mDseNewTabUrlManager != null) {
            mDseNewTabUrlManager.destroy();
        }

        super.onDestroyInternal();
    }

    @Override
    protected void destroyTabModels() {
        if (mTabModelOrchestrator != null) {
            mTabModelOrchestrator.destroy();
        }
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        if (ChromeApplicationImpl.isSevereMemorySignal(level)) {
            NativePageAssassin.getInstance().freezeAllHiddenPages();
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        Boolean result =
                KeyboardShortcuts.dispatchKeyEvent(
                        event,
                        mUIWithNativeInitialized,
                        getFullscreenManager(),
                        /* menuOrKeyboardActionController= */ this);
        return result != null ? result : super.dispatchKeyEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (!mUIWithNativeInitialized) {
            return super.onKeyDown(keyCode, event);
        }
        // Detecting a long press of the back button via onLongPress is broken in Android N.
        // To work around this, use a postDelayed, which is supported in all versions.
        if (keyCode == KeyEvent.KEYCODE_BACK
                && !isTablet()
                && !getFullscreenManager().getPersistentFullscreenMode()) {
            if (mShowHistoryRunnable == null) mShowHistoryRunnable = this::showFullHistorySheet;
            mHandler.postDelayed(mShowHistoryRunnable, ViewConfiguration.getLongPressTimeout());
            return super.onKeyDown(keyCode, event);
        }
        boolean isCurrentTabVisible =
                !isInOverviewMode() && (!isTablet() || getCurrentTabModel().getCount() != 0);
        boolean keyboardShortcutHandled =
                KeyboardShortcuts.onKeyDown(
                        event,
                        isCurrentTabVisible,
                        true,
                        getTabModelSelector(),
                        /* menuOrKeyboardActionController= */ this,
                        getToolbarManager());
        if (keyboardShortcutHandled) {
            RecordUserAction.record("MobileKeyboardShortcutUsed");
        }
        return keyboardShortcutHandled || super.onKeyDown(keyCode, event);
    }

    private void showFullHistorySheet() {
        ((TabbedRootUiCoordinator) mRootUiCoordinator).showFullHistorySheet();
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && !isTablet()) {
            mHandler.removeCallbacks(mShowHistoryRunnable);
            mShowHistoryRunnable = null;
            if (event.getEventTime() - event.getDownTime()
                            >= ViewConfiguration.getLongPressTimeout()
                    && NavigationSheet.isInstanceShowing(
                            mRootUiCoordinator.getBottomSheetController())) {
                // If tab history popup is showing, do not process the keyUp event
                // which will dismiss it immediately.
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    public boolean hasPendingNavigationRunnableForTesting() {
        ThreadUtils.assertOnUiThread();
        return mShowHistoryRunnable != null;
    }

    @Override
    public void onProvideKeyboardShortcuts(
            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
        data.addAll(KeyboardShortcuts.createShortcutGroup(this));
    }

    @VisibleForTesting
    public View getTabsView() {
        return getCompositorViewHolderSupplier().get();
    }

    @VisibleForTesting
    public LayoutManagerChrome getLayoutManager() {
        return (LayoutManagerChrome) getCompositorViewHolderSupplier().get().getLayoutManager();
    }

    @VisibleForTesting
    public OneshotSupplier<LayoutStateProvider> getLayoutStateProviderSupplier() {
        return mLayoutStateProviderSupplier;
    }

    public OneshotSupplier<TabSwitcher> getTabSwitcherSupplierForTesting() {
        return mTabSwitcherSupplier;
    }

    private ComposedBrowserControlsVisibilityDelegate getAppBrowserControlsVisibilityDelegate() {
        return mRootUiCoordinator.getAppBrowserControlsVisibilityDelegate();
    }

    // App Menu related code -----------------------------------------------------------------------

    @Override
    public boolean canShowAppMenu() {
        // The popup menu relies on the model created during the full UI initialization, so do not
        // attempt to show the menu until the UI creation has finished.
        if (!mUIWithNativeInitialized) return false;

        // If the current active tab is showing a tab modal dialog, an app menu shouldn't be shown
        // in any cases, e.g. when a hardware menu button is clicked.
        Tab tab = getActivityTab();
        if (tab != null && ChromeTabModalPresenter.isDialogShowing(tab)) return false;

        return super.canShowAppMenu();
    }

    @Override
    public boolean isInOverviewMode() {
        return mLayoutManager != null && mLayoutManager.isLayoutVisible(LayoutType.TAB_SWITCHER);
    }

    @Override
    public void onStart() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onStart")) {
            super.onStart();
        }
    }

    @Override
    public void onStop() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onStop")) {
            super.onStop();
        }
    }

    @Override
    public void onPause() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onPause")) {
            super.onPause();
        }
    }

    @Override
    public void onResume() {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onResume")) {
            super.onResume();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        try (TraceEvent e = TraceEvent.scoped("ChromeTabbedActivity.onActivityResult")) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public void performOnConfigurationChanged(Configuration newConfig) {
        try (TraceEvent e =
                TraceEvent.scoped("ChromeTabbedActivity.performOnConfigurationChanged")) {
            super.performOnConfigurationChanged(newConfig);
        }
    }

    @Override
    public void onSceneChange(Layout layout) {
        super.onSceneChange(layout);
        if (!layout.shouldDisplayContentOverlay()) mTabModelSelector.onTabsViewShown();
    }

    /** Writes the tab state to disk. */
    @VisibleForTesting
    public void saveState() {
        mTabModelOrchestrator.saveState();

        // Save whether the current tab is a search result page into preferences.
        Tab currentStandardTab = TabModelUtils.getCurrentTab(mTabModelSelector.getModel(false));
        ChromeSharedPreferences.getInstance()
                .writeBoolean(
                        ChromePreferenceKeys.IS_LAST_VISITED_TAB_SRP,
                        currentStandardTab != null
                                && UrlUtilitiesJni.get()
                                        .isGoogleSearchUrl(currentStandardTab.getUrl().getSpec()));
    }

    @Override
    public void onEnterVr() {
        super.onEnterVr();
        mControlContainer.setVisibility(View.INVISIBLE);
        if (mVrBrowserControlsVisibilityDelegate == null) {
            mVrBrowserControlsVisibilityDelegate =
                    new BrowserControlsVisibilityDelegate(BrowserControlsState.BOTH);
            getAppBrowserControlsVisibilityDelegate()
                    .addDelegate(mVrBrowserControlsVisibilityDelegate);
        }
        mVrBrowserControlsVisibilityDelegate.set(BrowserControlsState.HIDDEN);
    }

    @Override
    public void onExitVr() {
        super.onExitVr();
        mControlContainer.setVisibility(View.VISIBLE);
        if (mVrBrowserControlsVisibilityDelegate != null) {
            mVrBrowserControlsVisibilityDelegate.set(BrowserControlsState.BOTH);
        }
    }

    /**
     * Reports that a new tab launcher shortcut was selected or an action equivalent to a shortcut
     * was performed.
     * @param isIncognito Whether the shortcut or action created a new incognito tab.
     */
    @RequiresApi(Build.VERSION_CODES.N_MR1)
    private void reportNewTabShortcutUsed(boolean isIncognito) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) return;

        ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
        shortcutManager.reportShortcutUsed(
                isIncognito ? "new-incognito-tab-shortcut" : "new-tab-shortcut");
    }

    @Override
    public boolean handleMismatchedIndices(
            Activity activityAtRequestedIndex,
            boolean isActivityInAppTasks,
            boolean isActivityInSameTask) {
        boolean shouldHandleMismatch =
                (ChromeFeatureList.sTabWindowManagerIndexReassignmentActivityFinishing.isEnabled()
                                && activityAtRequestedIndex.isFinishing())
                        || (ChromeFeatureList.sTabWindowManagerIndexReassignmentActivityInSameTask
                                        .isEnabled()
                                && isActivityInSameTask)
                        || (ChromeFeatureList
                                        .sTabWindowManagerIndexReassignmentActivityNotInAppTasks
                                        .isEnabled()
                                && !isActivityInAppTasks);

        if (!shouldHandleMismatch
                || !(activityAtRequestedIndex
                        instanceof ChromeTabbedActivity tabbedActivityAtRequestedIndex)) {
            return false;
        }

        // Destroy the TabPersistentStore instance maintained by the activity at the requested
        // index. Save the tab state first to align with the current flow of execution when the
        // store is destroyed.
        var tabModelOrchestrator =
                tabbedActivityAtRequestedIndex.getTabModelOrchestratorSupplier().get();
        // If the two activities launched within a short span, simply destroy the persistent store
        // instance of the activity at the requested index, assuming no changes have been made to
        // the tab state during this time.
        long onCreateTimeDeltaMs =
                getOnCreateTimestampMs() - tabbedActivityAtRequestedIndex.getOnCreateTimestampMs();
        RecordHistogram.recordTimesHistogram(
                HISTOGRAM_MISMATCHED_INDICES_ACTIVITY_CREATION_TIME_DELTA, onCreateTimeDeltaMs);
        boolean shouldSaveState =
                tabbedActivityAtRequestedIndex.getLifecycleDispatcher().getCurrentActivityState()
                        < ActivityState.STOPPED_WITH_NATIVE;
        if (shouldSaveState
                && onCreateTimeDeltaMs
                        > MultiWindowUtils.BACK_TO_BACK_CTA_CREATION_TIMESTAMP_DIFF_THRESHOLD_MS
                                .getValue()) {
            // Save state only if #onStopWithNative() that invokes this, has not run yet.
            tabModelOrchestrator.getTabPersistentStore().saveState();
        }
        tabModelOrchestrator.destroyTabPersistentStore();

        // If the activity at the requested index is not finishing already, explicitly finish it.
        if (!activityAtRequestedIndex.isFinishing()) {
            activityAtRequestedIndex.finish();
        }
        return true;
    }

    public MultiInstanceManager getMultiInstanceMangerForTesting() {
        return mMultiInstanceManager;
    }

    @VisibleForTesting
    public ChromeNextTabPolicySupplier getNextTabPolicySupplier() {
        return (ChromeNextTabPolicySupplier) mNextTabPolicySupplier;
    }

    /** Returns whether to show a NTP as the home surface at startup on tablet in regular mode. */
    private boolean shouldShowNtpHomeSurfaceOnStartup() {
        if (mTabModelSelector.isIncognitoSelected()) return false;

        assert mInactivityTracker != null;
        return ReturnToChromeUtil.shouldShowNtpAsHomeSurfaceAtStartup(
                getIntent(), getSavedInstanceState(), mInactivityTracker);
    }

    /**
     * Creates an adapter between the toolbar's observer that takes a float and the format that the
     * hub expects which is a double.
     */
    private DoubleConsumer adaptOnToolbarAlphaChange() {
        return alpha -> {
            // If the manager is still null, it doesn't matter whatever is happening. Can safely
            // ignore any signal.
            @Nullable ToolbarManager toolbarManager = getToolbarManager();
            if (toolbarManager == null) {
                return;
            }

            toolbarManager
                    .getToolbarAlphaInOverviewObserver()
                    .onOverviewAlphaChanged((float) alpha);
        };
    }

    private void maybeShowTabSwitcherAfterTabModelLoad(Intent intent) {
        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.DATA_SHARING)) return;
        boolean shouldShowTabSwitcher =
                IntentUtils.safeHasExtra(intent, DataSharingNotificationManager.DATA_SHARING_EXTRA)
                        && IntentHandler.wasIntentSenderChrome(intent)
                        && !mTabModelSelector.isIncognitoSelected();
        if (!shouldShowTabSwitcher) {
            return;
        }
        GURL url =
                new GURL(
                        IntentUtils.safeGetStringExtra(
                                intent, DataSharingNotificationManager.DATA_SHARING_EXTRA));
        Runnable showJoinFlowRunnable =
                () -> {
                    mRootUiCoordinator.getDataSharingTabManager().initiateJoinFlow(url);
                };
        TabModelUtils.runOnTabStateInitialized(
                getTabModelSelectorSupplier().get(),
                mCallbackController.makeCancelable(
                        (tabModelSelectorReturn) -> {
                            assert tabModelSelectorReturn == getTabModelSelectorSupplier().get();
                            TabSwitcherUtils.navigateToTabSwitcher(
                                    mLayoutManager, /* animate= */ false, showJoinFlowRunnable);
                        }));
    }
}