[go: nahoru, domu]

blob: 90bb20710deb7460959ee541904151b0431837a1 [file] [log] [blame]
// 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.app;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.KeyguardManager;
import android.app.assist.AssistContent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Display.Mode;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.PowerMonitor;
import org.chromium.base.StrictModeContext;
import org.chromium.base.SysUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.cached_flags.CachedFlagsSafeMode;
import org.chromium.base.memory.MemoryPurgeManager;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
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.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.ActivityUtils;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivitySessionTracker;
import org.chromium.chrome.browser.ChromeApplicationImpl;
import org.chromium.chrome.browser.ChromeKeyboardVisibilityDelegate;
import org.chromium.chrome.browser.ChromeWindow;
import org.chromium.chrome.browser.DeferredStartupHandler;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.PlayServicesVersionInfo;
import org.chromium.chrome.browser.WarmupManager;
import org.chromium.chrome.browser.app.appmenu.AppMenuPropertiesDelegateImpl;
import org.chromium.chrome.browser.app.download.DownloadMessageUiDelegate;
import org.chromium.chrome.browser.app.flags.ChromeCachedFlags;
import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
import org.chromium.chrome.browser.app.tab_activity_glue.ReparentingDelegateFactory;
import org.chromium.chrome.browser.app.tab_activity_glue.TabReparentingController;
import org.chromium.chrome.browser.app.tabmodel.AsyncTabParamsManagerSingleton;
import org.chromium.chrome.browser.app.tabmodel.TabModelOrchestrator;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.back_press.CloseListenerManager;
import org.chromium.chrome.browser.banners.AppMenuVerbiage;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.PowerBookmarkUtils;
import org.chromium.chrome.browser.bookmarks.TabBookmarker;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManagerHandler;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager.ContextualSearchTabPromotionDelegate;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityCommonsModule;
import org.chromium.chrome.browser.dependency_injection.ChromeActivityComponent;
import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.dom_distiller.DomDistillerUIUtils;
import org.chromium.chrome.browser.download.DownloadManagerService;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.download.items.OfflineContentAggregatorNotificationBridgeUiFactory;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl;
import org.chromium.chrome.browser.firstrun.ForcedSigninProcessor;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSessionState;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManager;
import org.chromium.chrome.browser.fullscreen.BrowserControlsManagerSupplier;
import org.chromium.chrome.browser.fullscreen.FullscreenBackPressHandler;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.gsa.ContextReporter;
import org.chromium.chrome.browser.gsa.GSAAccountChangeListener;
import org.chromium.chrome.browser.gsa.GSAContextDisplaySelection;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.history.HistoryManagerUtils;
import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.init.ProcessInitializationHandler;
import org.chromium.chrome.browser.intents.BrowserIntentUtils;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingComponent;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingComponentFactory;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingComponentSupplier;
import org.chromium.chrome.browser.layouts.LayoutManagerAppUtils;
import org.chromium.chrome.browser.media.FullscreenVideoPictureInPictureController;
import org.chromium.chrome.browser.metrics.ActivityTabStartupMetricsTracker;
import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.UmaActivityObserver;
import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;
import org.chromium.chrome.browser.night_mode.WebContentsDarkModeController;
import org.chromium.chrome.browser.night_mode.WebContentsDarkModeMessageController;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.page_info.ChromePageInfo;
import org.chromium.chrome.browser.page_info.ChromePageInfoHighlight;
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.preferences.Pref;
import org.chromium.chrome.browser.printing.TabPrinter;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.readaloud.ReadAloudController;
import org.chromium.chrome.browser.selection.SelectionPopupBackPressHandler;
import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
import org.chromium.chrome.browser.share.ShareDelegate;
import org.chromium.chrome.browser.share.ShareDelegateImpl;
import org.chromium.chrome.browser.share.ShareDelegateSupplier;
import org.chromium.chrome.browser.stylus_handwriting.StylusWritingCoordinator;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.tab.RequestDesktopUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabHidingType;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabLoadIfNeededCaller;
import org.chromium.chrome.browser.tab.TabObscuringHandler;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab.TabState;
import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.tab.TabUtils.UseDesktopUserAgentCaller;
import org.chromium.chrome.browser.tabmodel.EmptyTabModel;
import org.chromium.chrome.browser.tabmodel.TabCreator;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabCreatorManagerSupplier;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelInitializer;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorProfileSupplier;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorSupplier;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.toolbar.ControlContainer;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.ui.BottomContainer;
import org.chromium.chrome.browser.ui.RootUiCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuBlocker;
import org.chromium.chrome.browser.ui.appmenu.AppMenuDelegate;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.device_lock.MissingDeviceLockLauncher;
import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeController;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarManageable;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManagerProvider;
import org.chromium.chrome.browser.ui.system.StatusBarColorController;
import org.chromium.components.browser_ui.accessibility.FontSizePrefs;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxyImpl;
import org.chromium.components.browser_ui.settings.SettingsLauncher;
import org.chromium.components.browser_ui.widget.InsetObserver;
import org.chromium.components.browser_ui.widget.InsetObserverSupplier;
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.Type;
import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
import org.chromium.components.browser_ui.widget.textbubble.TextBubble;
import org.chromium.components.browser_ui.widget.textbubble.TextBubbleBackPressHandler;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.page_info.PageInfoController.OpenedFromSource;
import org.chromium.components.policy.CombinedPolicyProvider;
import org.chromium.components.policy.CombinedPolicyProvider.PolicyChangeListener;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.profile_metrics.BrowserProfileType;
import org.chromium.components.sync.SyncService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.webapk.lib.client.WebApkValidator;
import org.chromium.components.webapps.AddToHomescreenCoordinator;
import org.chromium.components.webapps.InstallTrigger;
import org.chromium.components.webapps.bottomsheet.PwaBottomSheetController;
import org.chromium.components.webapps.bottomsheet.PwaBottomSheetControllerProvider;
import org.chromium.components.webapps.pwa_universal_install.PwaUniversalInstallBottomSheetCoordinator;
import org.chromium.components.webxr.XrDelegate;
import org.chromium.components.webxr.XrDelegateProvider;
import org.chromium.content_public.browser.DeviceUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.ScreenOrientationProvider;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.printing.PrintManagerDelegateImpl;
import org.chromium.printing.PrintingController;
import org.chromium.printing.PrintingControllerImpl;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.ApplicationViewportInsetSupplier;
import org.chromium.ui.base.Clipboard;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.display.DisplayAndroid;
import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
import org.chromium.ui.display.DisplayUtil;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.widget.Toast;
import org.chromium.url.GURL;
import org.chromium.webapk.lib.client.WebApkNavigationClient;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link AsyncInitializationActivity} that builds and manages a {@link CompositorViewHolder}
* and associated classes.
* @param <C> - type of associated Dagger component.
*/
public abstract class ChromeActivity<C extends ChromeActivityComponent>
extends AsyncInitializationActivity
implements TabCreatorManager,
PolicyChangeListener,
ContextualSearchTabPromotionDelegate,
SnackbarManageable,
SceneChangeObserver,
StatusBarColorController.StatusBarColorProvider,
AppMenuDelegate,
AppMenuBlocker,
MenuOrKeyboardActionController,
CompositorViewHolder.Initializer,
TabModelInitializer {
private static final String TAG = "ChromeActivity";
private static final int CONTENT_VIS_DELAY_MS = 5;
public static final String UNFOLD_LATENCY_BEGIN_TIMESTAMP = "unfold_latency_begin_timestamp";
private C mComponent;
/** Used to generate a unique ID for each ChromeActivity. */
private static long sNextActivityId;
private long mActivityId;
/** Used to access the {@link ShareDelegate} from {@link WindowAndroid}. */
private final UnownedUserDataSupplier<ShareDelegate> mShareDelegateSupplier =
new ShareDelegateSupplier();
private final ObservableSupplierImpl<TabModelOrchestrator> mTabModelOrchestratorSupplier =
new ObservableSupplierImpl<>();
/** Used to access the {@link TabModelSelector} from {@link WindowAndroid}. */
private final UnownedUserDataSupplier<TabModelSelector> mTabModelSelectorSupplier =
new TabModelSelectorSupplier();
/** Used to access the {@link TabCreatorManager} from {@link WindowAndroid}. */
private final UnownedUserDataSupplier<TabCreatorManager> mTabCreatorManagerSupplier =
new TabCreatorManagerSupplier();
private final ObservableSupplierImpl<EdgeToEdgeController> mEdgeToEdgeControllerSupplier =
new ObservableSupplierImpl<>();
private final UnownedUserDataSupplier<ManualFillingComponent> mManualFillingComponentSupplier =
new ManualFillingComponentSupplier();
// TODO(crbug.com/1209864): Move ownership to RootUiCoordinator.
private final UnownedUserDataSupplier<BrowserControlsManager> mBrowserControlsManagerSupplier =
new BrowserControlsManagerSupplier();
protected TabModelSelectorProfileSupplier mTabModelProfileSupplier =
new TabModelSelectorProfileSupplier(mTabModelSelectorSupplier);
protected final ObservableSupplierImpl<BookmarkModel> mBookmarkModelSupplier =
new ObservableSupplierImpl<>();
protected ObservableSupplierImpl<TabBookmarker> mTabBookmarkerSupplier =
new ObservableSupplierImpl<>();
private TabModelOrchestrator mTabModelOrchestrator;
private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
private ObservableSupplierImpl<TabContentManager> mTabContentManagerSupplier =
new ObservableSupplierImpl<>();
private TabContentManager mTabContentManager;
private final UmaActivityObserver mUmaActivityObserver;
private ContextReporter mContextReporter;
private boolean mPartnerBrowserRefreshNeeded;
/** Set if {@link #postDeferredStartupIfNeeded()} is called before native has loaded. */
private boolean mDeferredStartupQueued;
/** Whether or not {@link #postDeferredStartupIfNeeded()} has already successfully run. */
private boolean mDeferredStartupPosted;
private boolean mNativeInitialized;
private boolean mRemoveWindowBackgroundDone;
// Observes when sync becomes ready to create the mContextReporter.
private SyncService.SyncStateChangedListener mSyncStateChangedListener;
// The FullscreenVideoPictureInPictureController is initialized lazily https://crbug.com/729738.
private FullscreenVideoPictureInPictureController mFullscreenVideoPictureInPictureController;
private ObservableSupplierImpl<CompositorViewHolder> mCompositorViewHolderSupplier =
new ObservableSupplierImpl<>();
private ObservableSupplierImpl<LayoutManagerImpl> mLayoutManagerSupplier =
new ObservableSupplierImpl<>();
protected final UnownedUserDataSupplier<InsetObserver> mInsetObserverViewSupplier =
new InsetObserverSupplier();
private final ObservableSupplierImpl<ContextualSearchManager> mContextualSearchManagerSupplier =
new ObservableSupplierImpl<>();
private SnackbarManager mSnackbarManager;
// Timestamp in ms when initial layout inflation begins
private long mInflateInitialLayoutBeginMs;
// Timestamp in ms when initial layout inflation ends
private long mInflateInitialLayoutEndMs;
/** Whether or not a PolicyChangeListener was added. */
private boolean mDidAddPolicyChangeListener;
private ActivityTabStartupMetricsTracker mActivityTabStartupMetricsTracker;
/** A means of providing the foreground tab of the activity to different features. */
private final ActivityTabProvider mActivityTabProvider = new ActivityTabProvider();
/** Whether or not the activity is in started state. */
private boolean mStarted;
/** The current configuration, used to for diffing when the configuration is changed. */
private Configuration mConfig;
/** Supplier of the instance to control the tab-reparenting tasks. */
private OneshotSupplierImpl<TabReparentingController> mTabReparentingControllerSupplier =
new OneshotSupplierImpl<>();
/** Track whether {@link #mTabReparentingController} has prepared tab reparenting. */
private boolean mIsTabReparentingPrepared;
/** Listen to display change and start tab-reparenting if necessary. */
private DisplayAndroidObserver mDisplayAndroidObserver;
/**
* The RootUiCoordinator associated with the activity. This variable is held to facilitate
* testing.
* TODO(pnoland, https://crbug.com/865801): make this private again.
*/
protected RootUiCoordinator mRootUiCoordinator;
@Nullable private BottomContainer mBottomContainer;
private LaunchCauseMetrics mLaunchCauseMetrics;
private GSAAccountChangeListener mGSAAccountChangeListener;
// TODO(972867): Pull MenuOrKeyboardActionController out of ChromeActivity.
private List<MenuOrKeyboardActionController.MenuOrKeyboardActionHandler> mMenuActionHandlers =
new ArrayList<>();
// Whether this Activity is in Picture in Picture mode, based on the most recent call to
// {@link onPictureInPictureModeChanged} from the platform. This might disagree with the value
// returned by {@link isInPictureInPictureMode}.
private boolean mLastPictureInPictureModeForTesting;
protected BackPressManager mBackPressManager = new BackPressManager();
private TextBubbleBackPressHandler mTextBubbleBackPressHandler;
private SelectionPopupBackPressHandler mSelectionPopupBackPressHandler;
private Callback<TabModelSelector> mSelectionPopupBackPressInitCallback;
private CloseListenerManager mCloseListenerManager;
private StylusWritingCoordinator mStylusWritingCoordinator;
private boolean mBlockingDrawForAppRestart;
private Runnable mShowContentRunnable;
private boolean mIsRecreatingForTabletModeChange;
// This is only used on automotive.
private @Nullable MissingDeviceLockLauncher mMissingDeviceLockLauncher;
// Handling the dismissal of tab modal dialog.
private TabModalLifetimeHandler mTabModalLifetimeHandler;
private ViewGroup mBaseChromeLayout;
protected ChromeActivity() {
mManualFillingComponentSupplier.set(ManualFillingComponentFactory.createComponent());
sNextActivityId++;
mActivityId = sNextActivityId;
mUmaActivityObserver = new UmaActivityObserver(this);
}
private void incrementCounter(String key) {
// Increment a counter for sessions where Java code runs up to this
// point, with the counter to be reset in the native C++ code. Thus
// this serves as a diagnostic tool in the cases where the native C++
// code is not reached.
SharedPreferencesManager prefs = ChromeSharedPreferences.getInstance();
int count = prefs.readInt(key, 0);
// Note that this is written asynchronously, so there is a chance that
// this will not succeed.
prefs.writeInt(key, count + 1);
}
@Override
protected void onPreCreate() {
CachedFlagsSafeMode.getInstance().onStartOrResumeCheckpoint();
if (earlyInitializeStartupMetrics()) {
mActivityTabStartupMetricsTracker =
new ActivityTabStartupMetricsTracker(mActivityId, mTabModelSelectorSupplier);
}
super.onPreCreate();
initializeBackPressHandling();
}
private boolean earlyInitializeStartupMetrics() {
return ChromeFeatureList.sEarlyInitializeStartupMetrics.isEnabled();
}
@Override
protected void onAbortCreate() {
super.onAbortCreate();
CachedFlagsSafeMode.getInstance().onPauseCheckpoint();
}
@Override
protected void onPostCreate() {
incrementCounter(ChromePreferenceKeys.UMA_ON_POSTCREATE_COUNTER);
super.onPostCreate();
}
@Override
protected ActivityWindowAndroid createWindowAndroid() {
return new ChromeWindow(
/* activity= */ this,
mActivityTabProvider,
mCompositorViewHolderSupplier,
getModalDialogManagerSupplier(),
mManualFillingComponentSupplier,
getIntentRequestTracker());
}
@Override
public boolean onIntentCallbackNotFoundError(String error) {
createWindowErrorSnackbar(error, mSnackbarManager);
return true;
}
@VisibleForTesting
public static void createWindowErrorSnackbar(String error, SnackbarManager snackbarManager) {
if (snackbarManager != null) {
Snackbar snackbar =
Snackbar.make(
error, null, Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_WINDOW_ERROR);
snackbar.setSingleLine(false);
snackbar.setDuration(SnackbarManager.DEFAULT_SNACKBAR_DURATION_LONG_MS);
snackbarManager.showSnackbar(snackbar);
}
}
@Override
public void performPreInflationStartup() {
setupUnownedUserDataSuppliers();
if (BuildInfo.getInstance().isAutomotive
&& ChromeFeatureList.sVerticalAutomotiveBackButtonToolbar.isEnabled()) {
mBaseChromeLayout = new FrameLayout(this);
}
// Ensure that mConfig is initialized before tablet mode changes.
mConfig = getResources().getConfiguration();
// Make sure the root coordinator is created prior to calling super to ensure all
// the activity lifecycle events are called.
mRootUiCoordinator = createRootUiCoordinator();
mStylusWritingCoordinator =
new StylusWritingCoordinator(
this, getLifecycleDispatcher(), getActivityTabProvider());
// Create component before calling super to give its members a chance to catch
// onPreInflationStartup event.
mComponent = createComponent();
// Create the orchestrator that manages Tab models and persistence
mTabModelOrchestrator = createTabModelOrchestrator();
mTabModelOrchestratorSupplier.set(mTabModelOrchestrator);
// There's no corresponding call to removeObserver() for this addObserver() because
// mTabModelProfileSupplier has the same lifecycle as this activity.
mTabModelProfileSupplier.addObserver(
(profile) -> {
mBookmarkModelSupplier.set(
profile == null ? null : BookmarkModel.getForProfile(profile));
});
super.performPreInflationStartup();
// Force a partner customizations refresh if it has yet to be initialized. This can happen
// if Chrome is killed and you refocus a previous activity from Android recents, which does
// not go through ChromeLauncherActivity that would have normally triggered this.
mPartnerBrowserRefreshNeeded = !PartnerBrowserCustomizations.getInstance().isInitialized();
CommandLine commandLine = CommandLine.getInstance();
if (!commandLine.hasSwitch(ChromeSwitches.DISABLE_FULLSCREEN)) {
TypedValue threshold = new TypedValue();
getResources().getValue(R.dimen.top_controls_show_threshold, threshold, true);
commandLine.appendSwitchWithValue(
ContentSwitches.TOP_CONTROLS_SHOW_THRESHOLD,
threshold.coerceToString().toString());
getResources().getValue(R.dimen.top_controls_hide_threshold, threshold, true);
commandLine.appendSwitchWithValue(
ContentSwitches.TOP_CONTROLS_HIDE_THRESHOLD,
threshold.coerceToString().toString());
}
getWindow().setBackgroundDrawable(getBackgroundDrawable());
}
private void setupUnownedUserDataSuppliers() {
mShareDelegateSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
mTabModelSelectorSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
mTabCreatorManagerSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
mManualFillingComponentSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
mInsetObserverViewSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
mBrowserControlsManagerSupplier.attach(getWindowAndroid().getUnownedUserDataHost());
// BrowserControlsManager is ready immediately.
mBrowserControlsManagerSupplier.set(
new BrowserControlsManager(this, BrowserControlsManager.ControlsPosition.TOP));
}
/** Subclasses must create a {@link RootUiCoordinator}. */
protected abstract RootUiCoordinator createRootUiCoordinator();
private NotificationManagerProxy getNotificationManagerProxy() {
return new NotificationManagerProxyImpl(getApplicationContext());
}
private C createComponent() {
ChromeActivityCommonsModule.Factory overridenCommonsFactory =
ModuleFactoryOverrides.getOverrideFor(ChromeActivityCommonsModule.Factory.class);
ChromeActivityCommonsModule commonsModule =
overridenCommonsFactory == null
? new ChromeActivityCommonsModule(
this,
mRootUiCoordinator::getBottomSheetController,
getTabModelSelectorSupplier(),
getBrowserControlsManager(),
getBrowserControlsManager(),
getBrowserControlsManager(),
getFullscreenManager(),
getLayoutManagerSupplier(),
getLifecycleDispatcher(),
this::getSnackbarManager,
getProfileProviderSupplier(),
mActivityTabProvider,
getTabContentManager(),
getWindowAndroid(),
mCompositorViewHolderSupplier,
/* tabCreatorManager= */ this,
this::getCurrentTabCreator,
this::isCustomTab,
mRootUiCoordinator.getStatusBarColorController(),
ScreenOrientationProvider.getInstance(),
this::getNotificationManagerProxy,
getTabContentManagerSupplier(),
this::getActivityTabStartupMetricsTracker,
/* compositorViewHolderInitializer= */ this,
/* chromeActivityNativeDelegate= */ this,
getModalDialogManagerSupplier(),
getBrowserControlsManager(),
this::getSavedInstanceState,
mManualFillingComponentSupplier.get().getBottomInsetSupplier(),
getShareDelegateSupplier(),
/* tabModelInitializer= */ this,
getActivityType())
: overridenCommonsFactory.create(
this,
mRootUiCoordinator::getBottomSheetController,
getTabModelSelectorSupplier(),
getBrowserControlsManager(),
getBrowserControlsManager(),
getBrowserControlsManager(),
getFullscreenManager(),
getLayoutManagerSupplier(),
getLifecycleDispatcher(),
this::getSnackbarManager,
getProfileProviderSupplier(),
mActivityTabProvider,
getTabContentManager(),
getWindowAndroid(),
mCompositorViewHolderSupplier,
this,
this::getCurrentTabCreator,
this::isCustomTab,
mRootUiCoordinator.getStatusBarColorController(),
ScreenOrientationProvider.getInstance(),
this::getNotificationManagerProxy,
getTabContentManagerSupplier(),
this::getActivityTabStartupMetricsTracker,
/* CompositorViewHolder.Initializer */ this,
/* ChromeActivityNativeDelegate */ this,
getModalDialogManagerSupplier(),
getBrowserControlsManager(),
this::getSavedInstanceState,
mManualFillingComponentSupplier.get().getBottomInsetSupplier(),
getShareDelegateSupplier(),
/* tabModelInitializer= */ this,
getActivityType());
return createComponent(commonsModule);
}
/**
* Override this to create a component that represents a richer dependency graph for a
* particular subclass of ChromeActivity. The specialized component should be activity-scoped
* and include all modules for ChromeActivityComponent, such as
* {@link ChromeActivityCommonsModule}, along with any additional modules.
*
* You may immediately resolve some of the classes belonging to the component in this method.
*/
@SuppressWarnings("unchecked")
protected C createComponent(ChromeActivityCommonsModule commonsModule) {
return (C)
ChromeApplicationImpl.getComponent().createChromeActivityComponent(commonsModule);
}
/**
* @return the activity-scoped component associated with this instance of activity.
*/
public final C getComponent() {
return mComponent;
}
@SuppressLint("NewApi")
@Override
public void performPostInflationStartup() {
try (TraceEvent te = TraceEvent.scoped("ChromeActivity.performPostInflationStartup")) {
super.performPostInflationStartup();
Intent intent = getIntent();
if (0 != (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)) {
getLaunchCauseMetrics().onLaunchFromRecents();
} else {
getLaunchCauseMetrics().onReceivedIntent();
}
mBottomContainer = (BottomContainer) findViewById(R.id.bottom_container);
mSnackbarManager = new SnackbarManager(this, mBottomContainer, getWindowAndroid());
mInsetObserverViewSupplier.get().addObserver(mSnackbarManager);
SnackbarManagerProvider.attach(getWindowAndroid(), mSnackbarManager);
// Make the activity listen to policy change events
CombinedPolicyProvider.get().addPolicyChangeListener(this);
mDidAddPolicyChangeListener = true;
// Set up the animation placeholder to be the SurfaceView. This disables the
// SurfaceView's 'hole' clipping during animations that are notified to the window.
getWindowAndroid()
.setAnimationPlaceholderView(
mCompositorViewHolderSupplier.get().getCompositorView());
initializeTabModels();
if (isFinishing()) return;
TabModelSelector tabModelSelector = mTabModelOrchestrator.getTabModelSelector();
setTabContentManager(
new TabContentManager(
this,
mBrowserControlsManagerSupplier.get(),
!SysUtils.isLowEndDevice(),
tabModelSelector != null ? tabModelSelector::getTabById : null));
getBrowserControlsManager()
.initialize(
(ControlContainer) findViewById(R.id.control_container),
getActivityTabProvider(),
getTabModelSelector(),
getControlContainerHeightResource());
mBottomContainer.initialize(
getBrowserControlsManager(),
getWindowAndroid().getApplicationBottomInsetSupplier());
ShareDelegate shareDelegate =
new ShareDelegateImpl(
mRootUiCoordinator.getBottomSheetController(),
getLifecycleDispatcher(),
getActivityTabProvider(),
getTabModelSelectorSupplier(),
mTabModelProfileSupplier,
new ShareDelegateImpl.ShareSheetDelegate(),
isCustomTab());
mShareDelegateSupplier.set(shareDelegate);
TabBookmarker tabBookmarker =
new TabBookmarker(
this,
mBookmarkModelSupplier,
mRootUiCoordinator::getBottomSheetController,
this::getSnackbarManager,
isCustomTab());
mTabBookmarkerSupplier.set(tabBookmarker);
mShowContentRunnable =
() -> {
findViewById(android.R.id.content).setVisibility(View.VISIBLE);
mBlockingDrawForAppRestart = false;
};
// If onStart was called before postLayoutInflation (because inflation was done in a
// background thread) then make sure to call the relevant methods belatedly.
if (mStarted) {
mCompositorViewHolderSupplier.get().onStart();
}
}
}
@Override
protected void initializeStartupMetrics() {
// Initialize the activity session tracker as early as possible so that
// it can start background tasks.
ChromeActivitySessionTracker chromeActivitySessionTracker =
ChromeActivitySessionTracker.getInstance();
chromeActivitySessionTracker.registerTabModelSelectorSupplier(
this, mTabModelSelectorSupplier);
if (!earlyInitializeStartupMetrics()) {
mActivityTabStartupMetricsTracker =
new ActivityTabStartupMetricsTracker(mActivityId, mTabModelSelectorSupplier);
}
}
public ActivityTabStartupMetricsTracker getActivityTabStartupMetricsTracker() {
return mActivityTabStartupMetricsTracker;
}
@Override
protected View getViewToBeDrawnBeforeInitializingNative() {
View controlContainer = findViewById(R.id.control_container);
return controlContainer != null
? controlContainer
: super.getViewToBeDrawnBeforeInitializingNative();
}
/**
* This function triggers the layout inflation. If subclasses override {@link
* #doLayoutInflation}, no calls to {@link #getCompositorViewHolderSupplier().get()} can be done
* until inflation is complete and {@link #onInitialLayoutInflationComplete()} is called. If the
* subclass does not override {@link #doLayoutInflation}, then {@link
* #getCompositorViewHolderSupplier().get()} is safe to be called after calling super.
*/
@Override
protected final void triggerLayoutInflation() {
mInflateInitialLayoutBeginMs = SystemClock.elapsedRealtime();
try (TraceEvent te = TraceEvent.scoped("ChromeActivity.triggerLayoutInflation")) {
SelectionPopupController.setShouldGetReadbackViewFromWindowAndroid();
SelectionPopupController.setAllowSurfaceControlMagnifier();
enableHardwareAcceleration();
setLowEndTheme();
WarmupManager warmupManager = WarmupManager.getInstance();
if (warmupManager.hasViewHierarchyWithToolbar(getControlContainerLayoutId(), this)) {
View placeHolderView = new View(this);
setContentView(placeHolderView);
ViewGroup contentParent = (ViewGroup) placeHolderView.getParent();
warmupManager.transferViewHierarchyTo(contentParent);
contentParent.removeView(placeHolderView);
onInitialLayoutInflationComplete();
} else {
warmupManager.clearViewHierarchy();
doLayoutInflation();
}
}
}
/**
* This function implements the actual layout inflation, Subclassing Activities that override
* this method without calling super need to call {@link #onInitialLayoutInflationComplete()}.
*/
// TODO(crbug.com/1336778): Remove the @SuppressLint.
@SuppressLint("MissingInflatedId")
protected void doLayoutInflation() {
try (TraceEvent te = TraceEvent.scoped("ChromeActivity.doLayoutInflation")) {
// Allow disk access for the content view and toolbar container setup.
// On certain android devices this setup sequence results in disk writes outside
// of our control, so we have to disable StrictMode to work. See
// https://crbug.com/639352.
try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
TraceEvent.begin("setContentView(R.layout.main)");
if (mBaseChromeLayout != null) {
// Automotive devices override ChromeBaseAppCompatActivity#setContentView to add
// the automotive back button toolbar. This doesn't work if the layout uses
// <merge> tags, so we need to wrap R.layout.main in a ViewGroup first.
getLayoutInflater().inflate(R.layout.main, mBaseChromeLayout, true);
setContentView(mBaseChromeLayout);
} else {
setContentView(R.layout.main);
}
TraceEvent.end("setContentView(R.layout.main)");
if (getControlContainerLayoutId() != ActivityUtils.NO_RESOURCE_ID) {
ViewStub toolbarContainerStub =
((ViewStub) findViewById(R.id.control_container_stub));
toolbarContainerStub.setLayoutResource(getControlContainerLayoutId());
TraceEvent.begin("toolbarContainerStub.inflate");
toolbarContainerStub.inflate();
TraceEvent.end("toolbarContainerStub.inflate");
}
// It cannot be assumed that the result of toolbarContainerStub.inflate() will
// be the control container since it may be wrapped in another view.
ControlContainer controlContainer =
(ControlContainer) findViewById(R.id.control_container);
if (controlContainer == null) {
// omnibox_results_container_stub anchors off of control_container, and will
// crash during layout if control_container doesn't exist.
UiUtils.removeViewFromParent(findViewById(R.id.omnibox_results_container_stub));
}
// Inflate the correct toolbar layout for the device.
int toolbarLayoutId = getToolbarLayoutId();
if (toolbarLayoutId != ActivityUtils.NO_RESOURCE_ID && controlContainer != null) {
controlContainer.initWithToolbar(toolbarLayoutId);
}
}
onInitialLayoutInflationComplete();
}
}
@Override
protected void onInitialLayoutInflationComplete() {
mInflateInitialLayoutEndMs = SystemClock.elapsedRealtime();
mRootUiCoordinator.getStatusBarColorController().updateStatusBarColor();
ViewGroup rootView = (ViewGroup) getWindow().getDecorView().getRootView();
mCompositorViewHolderSupplier.set(
(CompositorViewHolder) findViewById(R.id.compositor_view_holder));
// If the UI was inflated on a background thread, then the CompositorView may not have been
// fully initialized yet as that may require the creation of a handler which is not allowed
// outside the UI thread. This call should fully initialize the CompositorView if it hasn't
// been yet.
mCompositorViewHolderSupplier.get().setRootView(rootView);
// Setting fitsSystemWindows to false ensures that the root view doesn't consume the
// insets.
rootView.setFitsSystemWindows(false);
// Add an inset observer that stores the insets to access later.
// WebContents needs the insets to determine the portion of the screen obscured by
// non-content displaying things such as the OSK.
mInsetObserverViewSupplier.set(new InsetObserver(rootView));
super.onInitialLayoutInflationComplete();
}
@Override
public boolean shouldStartGpuProcess() {
return true;
}
@Override
public final void initializeTabModels() {
if (areTabModelsInitialized()) return;
createTabModels();
TabModelSelector tabModelSelector = mTabModelOrchestrator.getTabModelSelector();
if (tabModelSelector == null) {
assert isFinishing();
return;
}
mTabModelSelectorSupplier.set(tabModelSelector);
mActivityTabProvider.setTabModelSelector(tabModelSelector);
mRootUiCoordinator.getStatusBarColorController().setTabModelSelector(tabModelSelector);
Pair<? extends TabCreator, ? extends TabCreator> tabCreators = createTabCreators();
mTabCreatorManagerSupplier.set(
incognito -> incognito ? tabCreators.second : tabCreators.first);
OfflinePageUtils.observeTabModelSelector(this, tabModelSelector);
if (mTabModelSelectorTabObserver != null) mTabModelSelectorTabObserver.destroy();
mTabModelSelectorTabObserver =
new TabModelSelectorTabObserver(tabModelSelector) {
@Override
public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
postDeferredStartupIfNeeded();
}
@Override
public void onPageLoadFinished(Tab tab, GURL url) {
postDeferredStartupIfNeeded();
OfflinePageUtils.showOfflineSnackbarIfNecessary(tab);
}
@Override
public void onCrash(Tab tab) {
postDeferredStartupIfNeeded();
}
};
}
/**
* @return The {@link TabModelOrchestrator} owned by this {@link ChromeActivity}.
*/
protected abstract TabModelOrchestrator createTabModelOrchestrator();
/** Call the {@link TabModelOrchestrator} to initialize its members. */
protected abstract void createTabModels();
/** Call the {@link TabModelOrchestrator} to destroy its members. */
protected abstract void destroyTabModels();
/**
* @return The {@link TabCreator}s owned
* by this {@link ChromeActivity}. The first item in the Pair is the normal model tab
* creator, and the second is the tab creator for incognito tabs.
*/
protected abstract Pair<? extends TabCreator, ? extends TabCreator> createTabCreators();
/**
* @return {@link ToolbarManager} that belongs to this activity or null if the current activity
* does not support a toolbar.
* TODO(pnoland, https://crbug.com/865801): remove this in favor of having RootUICoordinator
* inject ToolbarManager directly to sub-components.
*/
public @Nullable ToolbarManager getToolbarManager() {
return mRootUiCoordinator.getToolbarManager();
}
/**
* @return The {@link ManualFillingComponent} that belongs to this activity.
*/
public ManualFillingComponent getManualFillingComponent() {
return mManualFillingComponentSupplier.get();
}
/**
* @return The {@link LaunchCauseMetrics} to be owned by this {@link ChromeActivity}.
*/
protected abstract LaunchCauseMetrics createLaunchCauseMetrics();
private LaunchCauseMetrics getLaunchCauseMetrics() {
if (mLaunchCauseMetrics == null) {
mLaunchCauseMetrics = createLaunchCauseMetrics();
}
return mLaunchCauseMetrics;
}
@Override
public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new AppMenuPropertiesDelegateImpl(
this,
getActivityTabProvider(),
getMultiWindowModeStateDispatcher(),
getTabModelSelector(),
getToolbarManager(),
getWindow().getDecorView(),
null,
null,
mBookmarkModelSupplier,
/* incognitoReauthControllerOneshotSupplier= */ null,
mRootUiCoordinator.getReadAloudControllerSupplier());
}
/**
* @return The resource id for the layout to use for {@link ControlContainer}. 0 by default.
*/
protected int getControlContainerLayoutId() {
return ActivityUtils.NO_RESOURCE_ID;
}
/**
* @return The resource id that contains how large the browser controls are.
*/
public int getControlContainerHeightResource() {
return ActivityUtils.NO_RESOURCE_ID;
}
/**
* @return The layout ID for the toolbar to use.
*/
protected int getToolbarLayoutId() {
return ActivityUtils.NO_RESOURCE_ID;
}
@Override
public void initializeState() {
super.initializeState();
IntentHandler.setTestIntentsEnabled(
CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_TEST_INTENTS));
}
@Override
public void initializeCompositor() {
TraceEvent.begin("ChromeActivity:CompositorInitialization");
super.initializeCompositor();
getTabContentManager().initWithNative();
PrefService prefs = UserPrefs.get(getProfileProviderSupplier().get().getOriginalProfile());
mCompositorViewHolderSupplier
.get()
.onNativeLibraryReady(getWindowAndroid(), getTabContentManager(), prefs);
// TODO(1107916): Move contextual search initialization to the RootUiCoordinator.
if (ContextualSearchFieldTrial.isEnabled()) {
mContextualSearchManagerSupplier.set(
new ContextualSearchManager(
this,
this,
mRootUiCoordinator.getScrimCoordinator(),
getActivityTabProvider(),
getFullscreenManager(),
getBrowserControlsManager(),
getWindowAndroid(),
getTabModelSelectorSupplier().get(),
() -> getLastUserInteractionTime(),
getEdgeToEdgeSupplier()));
}
TraceEvent.end("ChromeActivity:CompositorInitialization");
}
@Override
public void onStartWithNative() {
assert mNativeInitialized : "onStartWithNative was called before native was initialized.";
super.onStartWithNative();
ChromeActivitySessionTracker.getInstance().onStartWithNative();
ChromeCachedFlags.getInstance().cacheNativeFlags();
// postDeferredStartupIfNeeded() is called in TabModelSelectorTabObsever#onLoadStopped(),
// #onPageLoadFinished() and #onCrash(). If we are not actively loading a tab (e.g.
// in Android N multi-instance, which is created by re-parenting an existing tab),
// ensure onDeferredStartup() gets called by calling postDeferredStartupIfNeeded() here.
if (mDeferredStartupQueued || shouldPostDeferredStartupForReparentedTab()) {
postDeferredStartupIfNeeded();
}
mRootUiCoordinator.restoreUiState(getSavedInstanceState());
}
/**
* Returns whether deferred startup should be run if we are not actively loading a tab (e.g.
* in Android N multi-instance, which is created by re-parenting an existing tab).
*/
public boolean shouldPostDeferredStartupForReparentedTab() {
return getActivityTab() == null || !getActivityTab().isLoading();
}
/** Allows derived activities to avoid showing the tab when the Activity is shown. */
protected boolean shouldShowTabOnActivityShown() {
return true;
}
private void onActivityShown() {
maybeRemoveWindowBackground();
Tab tab = getActivityTab();
if (tab != null) {
if (tab.isHidden() && shouldShowTabOnActivityShown()) {
tab.show(
TabSelectionType.FROM_USER,
TabLoadIfNeededCaller.ON_ACTIVITY_SHOWN_THEN_SHOW);
} else {
// The visible Tab's renderer process may have died after the activity was
// paused. Ensure that it's restored appropriately.
tab.loadIfNeeded(TabLoadIfNeededCaller.ON_ACTIVITY_SHOWN);
}
}
MultiWindowUtils.getInstance().recordMultiWindowStateUkm(this, tab);
}
private void onActivityHidden() {
Tab tab = getActivityTab();
TabModelSelector tabModelSelector = mTabModelOrchestrator.getTabModelSelector();
// If tab reparenting is in progress and the activity Tab isn't being reparented, e.g.
// because it's an NTP, skip hiding the Tab since it will be destroyed when the Activity is
// destroyed prior to recreation.
if (tab != null
&& ((tabModelSelector != null && !tabModelSelector.isReparentingInProgress())
|| AsyncTabParamsManagerSingleton.getInstance()
.hasParamsForTabId(tab.getId()))) {
tab.hide(TabHidingType.ACTIVITY_HIDDEN);
}
}
private boolean useWindowFocusForVisibility() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (useWindowFocusForVisibility()) {
if (hasFocus) {
onActivityShown();
} else {
if (ApplicationStatus.getStateForActivity(this) == ActivityState.STOPPED) {
onActivityHidden();
}
}
}
Clipboard.getInstance().onWindowFocusChanged(hasFocus);
}
/**
* Returns theme color which should be used when:
* - Web page does not provide a custom theme color.
* AND
* - Browser is in a state where it can be themed (no intersitial showing etc.)
* {@link TabState#UNSPECIFIED_THEME_COLOR} should be returned if the activity should use the
* default color in this scenario.
*/
public int getActivityThemeColor() {
return TabState.UNSPECIFIED_THEME_COLOR;
}
@Override
public int getBaseStatusBarColor(Tab tab) {
return StatusBarColorController.UNDEFINED_STATUS_BAR_COLOR;
}
private void createContextReporterIfNeeded() {
if (!mStarted) return; // Sync state reporting should work only in started state.
if (mContextReporter != null || getActivityTab() == null) return;
final SyncService syncService = getSyncServiceForOriginalProfile();
if (syncService != null && syncService.isSyncingUnencryptedUrls()) {
ContextReporter.SelectionReporter controller =
getContextualSearchManagerSupplier().hasValue()
? new ContextReporter.SelectionReporter() {
@Override
public void enable(Callback<GSAContextDisplaySelection> callback) {
getContextualSearchManagerSupplier()
.get()
.enableContextReporting(callback);
}
@Override
public void disable() {
getContextualSearchManagerSupplier()
.get()
.disableContextReporting();
}
}
: null;
mContextReporter =
AppHooks.get()
.createGsaHelper()
.getContextReporter(
getActivityTabProvider(),
mTabModelSelectorSupplier,
controller);
if (mSyncStateChangedListener != null) {
syncService.removeSyncStateChangedListener(mSyncStateChangedListener);
mSyncStateChangedListener = null;
}
return;
}
if (mSyncStateChangedListener == null && syncService != null) {
mSyncStateChangedListener = () -> createContextReporterIfNeeded();
syncService.addSyncStateChangedListener(mSyncStateChangedListener);
}
}
@Override
public void onResumeWithNative() {
// Close the current UMA record and start a new UMA one.
markSessionResume();
// Inform the actity lifecycle observers. Among other things, the observers record
// metrics pertaining to the "resumed" activity. This needs to happens after
// markSessionResume has closed the old UMA record, pertaining to the previous
// (backgrounded) activity, and opened a new one pertaining to the "resumed" activity.
super.onResumeWithNative();
// Resume the ChromeActivity...
RecordUserAction.record("MobileComeToForeground");
getLaunchCauseMetrics().setActivityId(mActivityId);
getLaunchCauseMetrics().recordLaunchCause();
Tab tab = getActivityTab();
if (tab != null) {
WebContents webContents = tab.getWebContents();
LaunchMetrics.commitLaunchMetrics(webContents);
// For picture-in-picture mode / auto-darken web contents.
if (webContents != null) webContents.notifyRendererPreferenceUpdate();
}
ChromeSessionState.setIsInMultiWindowMode(
MultiWindowUtils.getInstance().isInMultiWindowMode(this));
boolean appIsInNightMode = getNightModeStateProvider().isInNightMode();
boolean systemIsInNightMode = SystemNightModeMonitor.getInstance().isSystemNightModeOn();
ChromeSessionState.setDarkModeState(appIsInNightMode, systemIsInNightMode);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ensureFullscreenVideoPictureInPictureController();
}
if (mFullscreenVideoPictureInPictureController != null) {
mFullscreenVideoPictureInPictureController.onFrameworkExitedPictureInPicture();
}
getManualFillingComponent().onResume();
checkForDeviceLockOnAutomotive();
}
private void checkForDeviceLockOnAutomotive() {
if (BuildInfo.getInstance().isAutomotive) {
KeyguardManager keyguardManager =
(KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
RecordHistogram.recordBooleanHistogram(
"Android.Automotive.DeviceLockSet", keyguardManager.isDeviceSecure());
if (mMissingDeviceLockLauncher == null) {
mMissingDeviceLockLauncher =
new MissingDeviceLockLauncher(
this,
getProfileProviderSupplier().get().getOriginalProfile(),
getModalDialogManagerSupplier().get());
}
mMissingDeviceLockLauncher.checkPrivateDataIsProtectedByDeviceLock();
}
}
private void ensureFullscreenVideoPictureInPictureController() {
if (mFullscreenVideoPictureInPictureController == null) {
mFullscreenVideoPictureInPictureController =
new FullscreenVideoPictureInPictureController(
this, getActivityTabProvider(), getFullscreenManager());
}
}
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
getLaunchCauseMetrics().onUserLeaveHint();
// Can be in finishing state. No need to attempt PIP.
if (isActivityFinishingOrDestroyed()) return;
ensureFullscreenVideoPictureInPictureController();
mFullscreenVideoPictureInPictureController.attemptPictureInPicture();
// The attempt might not be successful. If it is, then `onPictureInPictureModeChanged` will
// let us know later. Note that the activity might report that it is in PictureInPicture
// mode at any point after this, which might be before we finish setup after receiving
// notification from mOnPictureInPictureModeChanged.
}
/**
* When we're notified that Picture-in-Picture mode has changed, make sure that the controller
* is kept up-to-date.
*/
@Override
@RequiresApi(api = Build.VERSION_CODES.O)
public void onPictureInPictureModeChanged(boolean inPicture, Configuration newConfig) {
super.onPictureInPictureModeChanged(inPicture, newConfig);
if (wasInPictureInPictureForMinimizedCustomTabs()) return;
if (inPicture) {
ensureFullscreenVideoPictureInPictureController();
mFullscreenVideoPictureInPictureController.onEnteredPictureInPictureMode();
mLastPictureInPictureModeForTesting = true;
} else if (mFullscreenVideoPictureInPictureController != null) {
mLastPictureInPictureModeForTesting = false;
mFullscreenVideoPictureInPictureController.onFrameworkExitedPictureInPicture();
}
}
/**
* Returns whether the {@link #onPictureInPictureModeChanged} call with `inPicture=true` was
* received because the Activity was put in picture-in-picture by the Minimized Custom Tabs
* feature. The other reason the Activity may be in PiP is because a fullscreen video was
* playing. The return value of this method is used to separate the handling of these cases.
* TODO(https://crbug.com/1507985): We should refactor how we handle PiP across different
* features.
*/
protected boolean wasInPictureInPictureForMinimizedCustomTabs() {
return false;
}
/**
* Return the status of a Picture-in-Picture transition. This is separate from
* {@link isInPictureInPictureMode}, because this will trigger only after we have received and
* processed an Activity.onPictureInPictureModeChanged call.
*/
public boolean getLastPictureInPictureModeForTesting() {
return mLastPictureInPictureModeForTesting;
}
@Override
public void onPauseWithNative() {
RecordUserAction.record("MobileGoToBackground");
Tab tab = getActivityTab();
if (tab != null) getTabContentManager().cacheTabThumbnail(tab);
getManualFillingComponent().onPause();
markSessionEnd();
super.onPauseWithNative();
}
@Override
public void onStopWithNative() {
if (GSAState.getInstance().isGsaAvailable() && !SysUtils.isLowEndDevice()) {
if (mGSAAccountChangeListener != null) mGSAAccountChangeListener.disconnect();
}
if (mSyncStateChangedListener != null) {
SyncService syncService = getSyncServiceForOriginalProfile();
if (syncService != null) {
syncService.removeSyncStateChangedListener(mSyncStateChangedListener);
}
mSyncStateChangedListener = null;
}
if (mContextReporter != null) mContextReporter.disable();
super.onStopWithNative();
}
@Override
public void onNewIntentWithNative(Intent intent) {
if (mFullscreenVideoPictureInPictureController != null) {
mFullscreenVideoPictureInPictureController.onFrameworkExitedPictureInPicture();
}
super.onNewIntentWithNative(intent);
getLaunchCauseMetrics().onReceivedIntent();
}
/**
* @return The type for this activity.
*/
public abstract @ActivityType int getActivityType();
/**
* @return Whether the given activity contains a CustomTab.
*/
public boolean isCustomTab() {
return getActivityType() == ActivityType.CUSTOM_TAB
|| getActivityType() == ActivityType.TRUSTED_WEB_ACTIVITY;
}
/**
* Actions that may be run at some point after startup. Place tasks that are not critical to the
* startup path here. This method will be called automatically.
*/
private void onDeferredStartup() {
initDeferredStartupForActivity();
ProcessInitializationHandler.getInstance().initializeDeferredStartupTasks();
DeferredStartupHandler.getInstance().queueDeferredTasksOnIdleHandler();
}
/**
* All deferred startup tasks that require the activity rather than the app should go here.
*
* Overriding methods should queue tasks on the DeferredStartupHandler before or after calling
* super depending on whether the tasks should run before or after these ones.
*/
@CallSuper
protected void initDeferredStartupForActivity() {
final String simpleName = getClass().getSimpleName();
DeferredStartupHandler.getInstance()
.addDeferredTask(
() -> {
if (isActivityFinishingOrDestroyed()) return;
if (getToolbarManager() != null) {
RecordHistogram.recordTimesHistogram(
"MobileStartup.ToolbarInflationTime." + simpleName,
mInflateInitialLayoutEndMs - mInflateInitialLayoutBeginMs);
getToolbarManager()
.onDeferredStartup(getOnCreateTimestampMs(), simpleName);
}
if (MultiWindowUtils.getInstance()
.isInMultiWindowMode(ChromeActivity.this)) {
onDeferredStartupForMultiWindowMode();
}
long intentTimestamp =
BrowserIntentUtils.getStartupRealtimeMillis(getIntent());
if (intentTimestamp != -1) {
recordIntentToCreationTime(
getOnCreateTimestampMs() - intentTimestamp);
}
recordDisplayDimensions();
int playServicesVersion = PlayServicesVersionInfo.getApkVersionNumber();
RecordHistogram.recordSparseHistogram(
"Android.PlayServices.Version", playServicesVersion);
FontSizePrefs.getInstance(
getProfileProviderSupplier().get().getOriginalProfile())
.recordUserFontPrefOnStartup();
});
DeferredStartupHandler.getInstance()
.addDeferredTask(
() -> {
if (isActivityFinishingOrDestroyed()) return;
ForcedSigninProcessor.checkCanSignIn(
ChromeActivity.this,
getProfileProviderSupplier().get().getOriginalProfile());
});
// GSA connection is not needed on low-end devices because Icing is disabled.
if (!SysUtils.isLowEndDevice()) {
DeferredStartupHandler.getInstance()
.addDeferredTask(
() -> {
if (isActivityFinishingOrDestroyed()
|| !GSAState.getInstance().isGsaAvailable()) {
return;
}
if (mGSAAccountChangeListener == null) {
mGSAAccountChangeListener =
GSAAccountChangeListener.create(
AppHooks.get().createGsaHelper());
}
mGSAAccountChangeListener.connect();
createContextReporterIfNeeded();
});
}
DeferredStartupHandler.getInstance()
.addDeferredTask(
() -> {
MemoryPurgeManager.getInstance().start();
});
}
/**
* Actions that may be run at some point after startup for Android N multi-window mode. Should
* be called from #onDeferredStartup() if the activity is in multi-window mode.
*/
private void onDeferredStartupForMultiWindowMode() {
// If the Activity was launched in multi-window mode, record a user action.
recordMultiWindowModeChanged(
/* isInMultiWindowMode= */ true, /* isDeferredStartup= */ true);
}
/**
* Records the time it takes from creating an intent for {@link ChromeActivity} to activity
* creation, including time spent in the framework.
* @param timeMs The time from creating an intent to activity creation.
*/
@CallSuper
protected void recordIntentToCreationTime(long timeMs) {
RecordHistogram.recordTimesHistogram("MobileStartup.IntentToCreationTime", timeMs);
}
@Override
public void onStart() {
// Sometimes mCompositorViewHolder is null, see crbug.com/1057613.
if (AsyncTabParamsManagerSingleton.getInstance().hasParamsWithTabToReparent()) {
// TODO(https://crbug.com/1252526): Remove logging once root cause of bug is identified
// & fixed.
Log.i(
TAG,
"#onStart, num async tabs: "
+ AsyncTabParamsManagerSingleton.getInstance()
.getAsyncTabParams()
.size());
if (mCompositorViewHolderSupplier.hasValue()) {
mCompositorViewHolderSupplier.get().prepareForTabReparenting();
}
}
super.onStart();
if (!useWindowFocusForVisibility()) {
onActivityShown();
}
if (mPartnerBrowserRefreshNeeded) {
mPartnerBrowserRefreshNeeded = false;
PartnerBrowserCustomizations.getInstance().initializeAsync(getApplicationContext());
PartnerBrowserCustomizations.getInstance()
.setOnInitializeAsyncFinished(
() -> {
if (PartnerBrowserCustomizations.isIncognitoDisabled()) {
terminateIncognitoSession();
}
});
}
if (mCompositorViewHolderSupplier.hasValue()) mCompositorViewHolderSupplier.get().onStart();
mStarted = true;
}
@Override
public void onResume() {
incrementCounter(ChromePreferenceKeys.UMA_ON_RESUME_COUNTER);
super.onResume();
}
/**
* WARNING: DO NOT USE THIS METHOD. PASS TabObscuringHandler TO THE OBJECT CONSTRUCTOR INSTEAD.
* @return {@link TabObscuringHandler} object.
*/
public TabObscuringHandler getTabObscuringHandler() {
if (mRootUiCoordinator == null) return null;
return mRootUiCoordinator.getTabObscuringHandler();
}
@Override
public void onStop() {
super.onStop();
if (useWindowFocusForVisibility()) {
if (!hasWindowFocus()) onActivityHidden();
} else {
onActivityHidden();
}
// We want to refresh partner browser provider every onStart().
mPartnerBrowserRefreshNeeded = true;
if (mCompositorViewHolderSupplier.hasValue()) mCompositorViewHolderSupplier.get().onStop();
// If postInflationStartup hasn't been called yet (because inflation was done asynchronously
// and has not yet completed), it no longer needs to do the belated onStart code since we
// were stopped in the mean time.
mStarted = false;
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
Tab tab = getActivityTab();
// No information is provided in incognito mode and overview mode.
if (tab != null && !tab.isIncognito() && !isInOverviewMode()) {
outContent.setWebUri(Uri.parse(tab.getUrl().getSpec()));
}
}
@Override
public long getOnCreateTimestampMs() {
return super.getOnCreateTimestampMs();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mIsRecreatingForTabletModeChange) {
outState.putLong(
UNFOLD_LATENCY_BEGIN_TIMESTAMP, getOnPauseBeforeFoldRecreateTimestampMs());
}
mRootUiCoordinator.onSaveInstanceState(outState, mIsRecreatingForTabletModeChange);
}
/**
* This cannot be overridden in order to preserve destruction order. Override
* {@link #onDestroyInternal()} instead to perform clean up tasks.
*/
@SuppressLint("NewApi")
@Override
protected final void onDestroy() {
if (mContextualSearchManagerSupplier.hasValue()) {
mContextualSearchManagerSupplier.get().destroy();
mContextualSearchManagerSupplier.set(null);
}
if (mSnackbarManager != null) {
SnackbarManagerProvider.detach(mSnackbarManager);
}
if (mBackPressManager != null) {
mBackPressManager.destroy();
}
if (mTabModelSelectorTabObserver != null) {
mTabModelSelectorTabObserver.destroy();
mTabModelSelectorTabObserver = null;
}
// TODO(1168131): Destruction and detaching of the LayoutManager should be moved to the
// RootUiCoordinator.
if (mLayoutManagerSupplier.get() != null) {
LayoutManagerAppUtils.detach(mLayoutManagerSupplier.get());
}
if (mCompositorViewHolderSupplier.hasValue()) {
CompositorViewHolder compositorViewHolder = mCompositorViewHolderSupplier.get();
if (compositorViewHolder.getLayoutManager() != null) {
compositorViewHolder.getLayoutManager().removeSceneChangeObserver(this);
}
compositorViewHolder.shutDown();
mCompositorViewHolderSupplier.set(null);
}
onDestroyInternal();
if (mDidAddPolicyChangeListener) {
CombinedPolicyProvider.get().removePolicyChangeListener(this);
mDidAddPolicyChangeListener = false;
}
if (mTabContentManager != null) {
mTabContentManager.destroy();
mTabContentManager = null;
}
if (mTabContentManagerSupplier != null) {
mTabContentManagerSupplier = null;
}
if (mManualFillingComponentSupplier.hasValue()) {
mManualFillingComponentSupplier.get().destroy();
}
mManualFillingComponentSupplier.destroy();
if (mBrowserControlsManagerSupplier.hasValue()) {
mBrowserControlsManagerSupplier.get().destroy();
}
mBrowserControlsManagerSupplier.destroy();
if (mActivityTabStartupMetricsTracker != null) {
mActivityTabStartupMetricsTracker.destroy();
mActivityTabStartupMetricsTracker = null;
}
destroyTabModels();
mBookmarkModelSupplier.set(null);
if (mShareDelegateSupplier != null) {
mShareDelegateSupplier.destroy();
}
if (mTabModelSelectorSupplier != null) {
mTabModelSelectorSupplier.destroy();
}
if (mBottomContainer != null) {
mBottomContainer.destroy();
mBottomContainer = null;
}
if (mDisplayAndroidObserver != null) {
getWindowAndroid().getDisplay().removeObserver(mDisplayAndroidObserver);
mDisplayAndroidObserver = null;
}
if (mTextBubbleBackPressHandler != null) {
mTextBubbleBackPressHandler.destroy();
mTextBubbleBackPressHandler = null;
}
if (mSelectionPopupBackPressHandler != null) {
mSelectionPopupBackPressHandler.destroy();
mSelectionPopupBackPressHandler = null;
}
if (mCloseListenerManager != null) {
mCloseListenerManager.destroy();
mCloseListenerManager = null;
}
if (mStylusWritingCoordinator != null) {
mStylusWritingCoordinator.destroy();
mStylusWritingCoordinator = null;
}
if (mInsetObserverViewSupplier.get() != null) {
mInsetObserverViewSupplier.get().removeObserver(mSnackbarManager);
}
// Destroy spare tab on activity destruction.
WarmupManager warmupManager = WarmupManager.getInstance();
warmupManager.destroySpareTab();
mActivityTabProvider.destroy();
ChromeActivitySessionTracker.getInstance().unregisterTabModelSelectorSupplier(this);
mComponent = null;
super.onDestroy();
}
/**
* Override this to perform destruction tasks. Note that by the time this is called, the
* {@link CompositorViewHolder} will be destroyed, but the {@link WindowAndroid} and
* {@link TabModelSelector} will not.
* <p>
* After returning from this, the {@link TabModelSelector} will be destroyed followed
* by the {@link WindowAndroid}.
*/
protected void onDestroyInternal() {}
/**
* @return The unified manager for all snackbar related operations.
*/
@Override
public SnackbarManager getSnackbarManager() {
BottomSheetController controller =
mRootUiCoordinator == null ? null : mRootUiCoordinator.getBottomSheetController();
if (mRootUiCoordinator != null
&& controller != null
&& controller.isSheetOpen()
&& !controller.isSheetHiding()) {
return mRootUiCoordinator.getBottomSheetSnackbarManager();
}
return mSnackbarManager;
}
@Override
protected ModalDialogManager createModalDialogManager() {
var dialogManager =
new ModalDialogManager(
new AppModalPresenter(this), ModalDialogManager.ModalDialogType.APP);
// TODO(crbug.com/1157310): Transition this::method refs to dedicated suppliers.
if (supportsTabModalDialogs()) {
mTabModalLifetimeHandler =
new TabModalLifetimeHandler(
this,
getLifecycleDispatcher(),
dialogManager,
() -> mRootUiCoordinator.getAppBrowserControlsVisibilityDelegate(),
this::getTabObscuringHandler,
this::getToolbarManager,
getContextualSearchManagerSupplier(),
getTabModelSelectorSupplier(),
this::getBrowserControlsManager,
this::getFullscreenManager,
mBackPressManager);
}
return dialogManager;
}
/**
* Whether tab modal dialog is supported. If not, a dialog will be shown as a App modal dialog.
*
* @return True if tab modal dialog is supported.
*/
protected boolean supportsTabModalDialogs() {
return false;
}
@Nullable
protected TabModalLifetimeHandler getTabModalLifetimeHandler() {
return mTabModalLifetimeHandler;
}
protected Drawable getBackgroundDrawable() {
// Set the window background to black on R cars to better blend in with the keyboard
// background and minimize flickering - More context on b/302039878.
if (BuildInfo.getInstance().isAutomotive
&& Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
return new ColorDrawable(getColor(R.color.baseline_neutral_0));
}
return new ColorDrawable(getColor(R.color.window_background_color));
}
/**
* Change the Window background color that will be used as the resizing background color on
* Android N+ multi-window mode. Note that subclasses can override this behavior accordingly in
* case there is already a Window background Drawable and don't want it to be replaced with the
* ColorDrawable.
*/
protected void changeBackgroundColorForResizing() {
getWindow()
.setBackgroundDrawable(
new ColorDrawable(getColor(R.color.window_background_color)));
}
private void maybeRemoveWindowBackground() {
// Only need to do this logic once.
if (mRemoveWindowBackgroundDone) return;
// Remove the window background only after native init and window getting focus. It's done
// after native init because before native init, a fake background gets shown. The window
// focus dependency is because doing it earlier can cause drawing bugs, e.g. crbug/673831.
if (!mNativeInitialized || !hasWindowFocus()) return;
// The window background color is used as the resizing background color in Android N+
// multi-window mode. See crbug.com/602366.
changeBackgroundColorForResizing();
mRemoveWindowBackgroundDone = true;
}
@Override
public void finishNativeInitialization() {
mNativeInitialized = true;
OfflineContentAggregatorNotificationBridgeUiFactory.instance();
maybeRemoveWindowBackground();
DownloadManagerService.getDownloadManagerService()
.onActivityLaunched(new DownloadMessageUiDelegate());
PowerMonitor.create();
super.finishNativeInitialization();
mManualFillingComponentSupplier
.get()
.initialize(
getWindowAndroid(),
mRootUiCoordinator.getBottomSheetController(),
(ChromeKeyboardVisibilityDelegate) getWindowAndroid().getKeyboardDelegate(),
mBackPressManager,
mEdgeToEdgeControllerSupplier,
findViewById(R.id.keyboard_accessory_sheet_stub),
findViewById(R.id.keyboard_accessory_stub));
mTabReparentingControllerSupplier.set(
new TabReparentingController(
ReparentingDelegateFactory.createReparentingControllerDelegate(
getTabModelSelector()),
AsyncTabParamsManagerSingleton.getInstance()));
// This must be initialized after initialization of tab reparenting controller.
DisplayAndroid display = getWindowAndroid().getDisplay();
mDisplayAndroidObserver =
new DisplayAndroidObserver() {
@Override
public void onDisplayModesChanged(List<Mode> supportedModes) {
maybeOnScreenSizeChange();
}
@Override
public void onCurrentModeChanged(Mode currentMode) {
if (!mBlockingDrawForAppRestart && getTabletMode().changed) {
mBlockingDrawForAppRestart = true;
findViewById(android.R.id.content).setVisibility(View.INVISIBLE);
showContent();
}
maybeOnScreenSizeChange();
}
};
display.addObserver(mDisplayAndroidObserver);
}
private boolean maybeOnScreenSizeChange() {
TabletMode tabletMode = getTabletMode();
if (tabletMode.changed) {
return onScreenLayoutSizeChange(tabletMode.isTablet);
}
return false;
}
/**
* @return Whether native initialization has been completed for this activity.
*/
public boolean didFinishNativeInitialization() {
return mNativeInitialized;
}
@Override
public boolean onOptionsItemSelected(int itemId, @Nullable Bundle menuItemData) {
if (mManualFillingComponentSupplier.hasValue()) {
mManualFillingComponentSupplier.get().dismiss();
}
return onMenuOrKeyboardAction(itemId, true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null) {
if (onOptionsItemSelected(item.getItemId(), null)) return true;
}
return super.onOptionsItemSelected(item);
}
/**
* @return Whether the activity is in overview mode.
*/
public boolean isInOverviewMode() {
return false;
}
/** Returns whether grid Tab switcher or the Start surface should be shown at startup. */
public boolean shouldShowOverviewPageOnStart(Intent intent) {
return false;
}
@CallSuper
@Override
public boolean canShowAppMenu() {
if (isActivityFinishingOrDestroyed()) return false;
@ActivityState int state = ApplicationStatus.getStateForActivity(this);
boolean inMultiWindow = MultiWindowUtils.getInstance().isInMultiWindowMode(this);
if (state != ActivityState.RESUMED && (!inMultiWindow || state != ActivityState.PAUSED)) {
return false;
}
return true;
}
/**
* @return Whether the tab models have been fully initialized.
*/
public boolean areTabModelsInitialized() {
return mTabModelOrchestrator != null && mTabModelOrchestrator.areTabModelsInitialized();
}
/**
* {@link TabModelSelector} no longer implements TabModel. Use getTabModelSelector() or
* getCurrentTabModel() depending on your needs.
* @return The {@link TabModelSelector}, possibly null.
* @deprecated in favor of getTabModelSelectorSupplier.
*/
@Deprecated
public TabModelSelector getTabModelSelector() {
if (!areTabModelsInitialized()) {
throw new IllegalStateException(
"Attempting to access TabModelSelector before initialization");
}
return mTabModelOrchestrator.getTabModelSelector();
}
/** Returns an {@link ObservableSupplier} for {@link TabModelOrchestrator}. */
public final ObservableSupplier<TabModelOrchestrator> getTabModelOrchestratorSupplier() {
return mTabModelOrchestratorSupplier;
}
/**
* Returns an {@link ObservableSupplier} for {@link TabModelSelector}. Prefer this method over
* using {@link #getTabModelSelector()} directly.
*/
public final ObservableSupplier<TabModelSelector> getTabModelSelectorSupplier() {
return mTabModelSelectorSupplier;
}
/**
* @return The provider of the visible tab in the current activity.
*/
public ActivityTabProvider getActivityTabProvider() {
return mActivityTabProvider;
}
/**
* @return The provider of the instance of {@link TabReparentingController}.
*/
protected OneshotSupplier<TabReparentingController> getTabReparentingControllerSupplier() {
return mTabReparentingControllerSupplier;
}
/** Gets the supplier of the {@link TabCreatorManager} instance. */
public ObservableSupplier<TabCreatorManager> getTabCreatorManagerSupplier() {
return mTabCreatorManagerSupplier;
}
/**
* @return a supplier for the {@link EdgeToEdgeController} that supports drawing to the edge of
* the screen.
*/
protected final ObservableSupplierImpl<EdgeToEdgeController> getEdgeToEdgeSupplier() {
return mEdgeToEdgeControllerSupplier;
}
@Override
public TabCreator getTabCreator(boolean incognito) {
if (!areTabModelsInitialized()) {
throw new IllegalStateException(
"Attempting to access TabCreator before initialization");
}
return mTabCreatorManagerSupplier.get().getTabCreator(incognito);
}
/**
* Convenience method that returns a tab creator for the currently selected {@link TabModel}.
* @return A tab creator for the currently selected {@link TabModel}.
*/
public TabCreator getCurrentTabCreator() {
return getTabCreator(getTabModelSelector().isIncognitoSelected());
}
/**
* Gets the {@link TabContentManager} instance which holds snapshots of the tabs in this model.
*
* @return The thumbnail cache, possibly null.
* @deprecated in favor of getTabContentManagerSupplier().
*/
@Deprecated
public TabContentManager getTabContentManager() {
return mTabContentManager;
}
/**
* Sets the {@link TabContentManager} owned by this {@link ChromeActivity}.
* @param tabContentManager A {@link TabContentManager} instance.
*/
private void setTabContentManager(TabContentManager tabContentManager) {
mTabContentManager = tabContentManager;
TabContentManagerHandler.create(
tabContentManager, getFullscreenManager(), getTabModelSelector());
mTabContentManagerSupplier.set(tabContentManager);
}
/** Gets the supplier of the {@link TabContentManager} instance. */
public ObservableSupplier<TabContentManager> getTabContentManagerSupplier() {
return mTabContentManagerSupplier;
}
/**
* Gets the current (inner) TabModel. This is a convenience function for
* getModelSelector().getCurrentModel(). It is *not* equivalent to the former getModel()
* @return Never null, if modelSelector or its field is uninstantiated returns a
* {@link EmptyTabModel} singleton
*/
public TabModel getCurrentTabModel() {
TabModelSelector modelSelector = getTabModelSelector();
if (modelSelector == null) return EmptyTabModel.getInstance(false);
return modelSelector.getCurrentModel();
}
/**
* DEPRECATED: Instead, use/hold a reference to {@link #mActivityTabProvider}. See
* https://crbug.com/871279 for more details. Note that there are important
* functional differences between {@link ActivityTabProvider} and this function
* when transitioning to/from the tab switcher. For a drop-in replacement, use
* {@link TabModelSelector#getCurrentTab} instead.
*
* Returns the tab being displayed by this ChromeActivity instance. This allows differentiation
* between ChromeActivity subclasses that swap between multiple tabs (e.g. ChromeTabbedActivity)
* and subclasses that only display one Tab (e.g. DocumentActivity).
*
* The default implementation grabs the tab currently selected by the TabModel, which may be
* null if the Tab does not exist or the system is not initialized.
*/
public Tab getActivityTab() {
if (!areTabModelsInitialized()) {
return null;
}
return TabModelUtils.getCurrentTab(getCurrentTabModel());
}
/**
* @return The current WebContents, or null if the tab does not exist or is not showing a
* WebContents.
*/
public WebContents getCurrentWebContents() {
if (!areTabModelsInitialized()) {
return null;
}
return TabModelUtils.getCurrentWebContents(getCurrentTabModel());
}
/**
* Gets the browser controls manager, creates it unless already created.
* @deprecated Instead, inject this directly to your constructor. If that's not possible, then
* use {@link BrowserControlsManagerSupplier}.
*/
@NonNull
@Deprecated
public BrowserControlsManager getBrowserControlsManager() {
if (!mBrowserControlsManagerSupplier.hasValue() && isActivityFinishingOrDestroyed()) {
// BrowserControlsManagerSupplier should always have a value unless it's in the process
// of destruction (and in that case, nothing should be called this method).
throw new IllegalStateException();
}
assert mBrowserControlsManagerSupplier.hasValue();
return mBrowserControlsManagerSupplier.get();
}
/**
* @return Fullscreen manager object.
*/
public @NonNull FullscreenManager getFullscreenManager() {
return getBrowserControlsManager().getFullscreenManager();
}
/**
* @return The {@code ContextualSearchManager} or {@code null} if none;
*/
public ObservableSupplier<ContextualSearchManager> getContextualSearchManagerSupplier() {
return mContextualSearchManagerSupplier;
}
/**
* Exits the fullscreen mode, if any. Does nothing if no fullscreen is present.
* @return Whether the fullscreen mode is currently showing.
*/
public boolean exitFullscreenIfShowing() {
FullscreenManager fullscreenManager = getFullscreenManager();
if (fullscreenManager.getPersistentFullscreenMode()) {
fullscreenManager.exitPersistentFullscreenMode();
return true;
}
return false;
}
@Override
public void initializeCompositorContent(
LayoutManagerImpl layoutManager,
View urlBar,
ViewGroup contentContainer,
ControlContainer controlContainer) {
// TODO(1168131): The responsibility of managing the availability of the LayoutManager
// should be moved to the RootUiCoordinator.
LayoutManagerAppUtils.attach(getWindowAndroid(), layoutManager);
mLayoutManagerSupplier.set(layoutManager);
layoutManager.addSceneChangeObserver(this);
CompositorViewHolder compositorViewHolder = mCompositorViewHolderSupplier.get();
compositorViewHolder.setLayoutManager(layoutManager);
compositorViewHolder.setFocusable(false);
compositorViewHolder.setControlContainer(controlContainer);
compositorViewHolder.setBrowserControlsManager(mBrowserControlsManagerSupplier.get());
compositorViewHolder.setUrlBar(urlBar);
ApplicationViewportInsetSupplier insetSupplier =
getWindowAndroid().getApplicationBottomInsetSupplier();
insetSupplier.setKeyboardInsetSupplier(
mInsetObserverViewSupplier.get().getSupplierForKeyboardInset());
insetSupplier.setKeyboardAccessoryInsetSupplier(
mManualFillingComponentSupplier.get().getBottomInsetSupplier());
compositorViewHolder.setApplicationViewportInsetSupplier(insetSupplier);
compositorViewHolder.setTopUiThemeColorProvider(
mRootUiCoordinator.getTopUiThemeColorProvider());
compositorViewHolder.onFinishNativeInitialization(getTabModelSelector(), this);
SwipeHandler swipeHandler = layoutManager.getToolbarSwipeHandler();
if (controlContainer != null
&& DeviceClassManager.enableToolbarSwipe()
&& swipeHandler != null) {
controlContainer.setSwipeHandler(swipeHandler);
}
mActivityTabProvider.setLayoutStateProvider(layoutManager);
if (mContextualSearchManagerSupplier.hasValue()) {
if (getProfileProviderSupplier().hasValue()) {
initializeContextualSearchManager(
layoutManager,
contentContainer,
compositorViewHolder,
getProfileProviderSupplier().get().getOriginalProfile());
} else {
getProfileProviderSupplier()
.onAvailable(
(profileProvider) -> {
initializeContextualSearchManager(
layoutManager,
contentContainer,
compositorViewHolder,
profileProvider.getOriginalProfile());
});
}
}
}
private void initializeContextualSearchManager(
LayoutManagerImpl layoutManager,
ViewGroup contentContainer,
CompositorViewHolder compositorViewHolder,
Profile profile) {
mContextualSearchManagerSupplier
.get()
.initialize(
contentContainer,
profile,
layoutManager,
mRootUiCoordinator.getBottomSheetController(),
compositorViewHolder,
getControlContainerHeightResource() == ActivityUtils.NO_RESOURCE_ID
? 0f
: getResources().getDimension(getControlContainerHeightResource()),
getToolbarManager(),
getActivityType(),
getIntentRequestTracker());
}
/**
* @return An {@link ObservableSupplier} that will supply the {@link LayoutManagerImpl} when it
* is ready.
*/
public ObservableSupplier<LayoutManagerImpl> getLayoutManagerSupplier() {
return mLayoutManagerSupplier;
}
/**
* @return An {@link ObservableSupplier} that will supply the {@link ShareDelegate} when
* it is ready.
*/
public ObservableSupplier<ShareDelegate> getShareDelegateSupplier() {
return mShareDelegateSupplier;
}
/**
* @return An {@link ObservableSupplier} that will supply the {@link CompositorViewHolder} when
* it is ready.
*/
public ObservableSupplier<CompositorViewHolder> getCompositorViewHolderSupplier() {
return mCompositorViewHolderSupplier;
}
/**
* Called when the back button is pressed.
* @return Whether or not the back button was handled.
*/
protected abstract boolean handleBackPressed();
/**
* @return If no higher priority back actions occur, whether pressing the back button
* would result in closing the tab. A true return value does not guarantee that
* a subsequent call to {@link #handleBackPressed()} will close the tab.
*/
public boolean backShouldCloseTab(Tab tab) {
return false;
}
@Override
public void performOnConfigurationChanged(Configuration newConfig) {
super.performOnConfigurationChanged(newConfig);
if (mConfig != null) {
if (mTabReparentingControllerSupplier.get() != null && maybeOnScreenSizeChange()) {
return;
}
// For UI mode type, we only need to recreate for TELEVISION to update refresh rate.
// Note that if UI mode night changes, with or without other changes, we will
// still recreate() when we get a callback from the
// ChromeBaseAppCompatActivity#onNightModeStateChanged or the overridden method in
// sub-classes if necessary.
if (didChangeUiModeType(
mConfig.uiMode, newConfig.uiMode, Configuration.UI_MODE_TYPE_TELEVISION)) {
recreate();
return;
}
if (newConfig.orientation != mConfig.orientation) {
RequestDesktopUtils.recordScreenOrientationChangedUkm(
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE,
getActivityTab());
}
}
mConfig = newConfig;
}
// Triggers runnable that makes content visible.
private void showContent() {
if (!mBlockingDrawForAppRestart || mShowContentRunnable == null) return;
mHandler.postDelayed(mShowContentRunnable, CONTENT_VIS_DELAY_MS);
}
// Checks whether the given uiModeTypes were present on oldUiMode or newUiMode but not the
// other.
private static boolean didChangeUiModeType(int oldUiMode, int newUiMode, int uiModeType) {
return isInUiModeType(oldUiMode, uiModeType) != isInUiModeType(newUiMode, uiModeType);
}
private static boolean isInUiModeType(int uiMode, int uiModeType) {
return (uiMode & Configuration.UI_MODE_TYPE_MASK) == uiModeType;
}
/**
* Called by the system when the activity changes from fullscreen mode to multi-window mode
* and visa-versa.
* @param isInMultiWindowMode True if the activity is in multi-window mode.
*/
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
// If native is not initialized, the multi-window user action will be recorded in
// #onDeferredStartupForMultiWindowMode() and CachedFeatureFlags#setIsInMultiWindowMode()
// will be called in #onResumeWithNative(). Both of these methods require native to be
// initialized, so do not call here to avoid crashing. See https://crbug.com/797921.
if (mNativeInitialized) {
recordMultiWindowModeChanged(isInMultiWindowMode, /* isDeferredStartup= */ false);
if (!isInMultiWindowMode
&& ApplicationStatus.getStateForActivity(this) == ActivityState.RESUMED) {
// Start a new UMA session when exiting multi-window mode if the activity is
// currently resumed. When entering multi-window Android recents gains focus, so
// ChromeActivity will get a call to onPauseWithNative(), ending the current UMA
// session. When exiting multi-window, however, if ChromeActivity is resumed it
// stays in that state.
markSessionEnd();
markSessionResume();
ChromeSessionState.setIsInMultiWindowMode(
MultiWindowUtils.getInstance().isInMultiWindowMode(this));
}
}
super.onMultiWindowModeChanged(isInMultiWindowMode);
}
/**
* Records user actions and ukms associated with entering and exiting Android N multi-window
* mode.
* @param isInMultiWindowMode True if the activity is in multi-window mode.
* @param isDeferredStartup True if the activity is deferred startup.
*/
private void recordMultiWindowModeChanged(
boolean isInMultiWindowMode, boolean isDeferredStartup) {
MultiWindowUtils.getInstance()
.recordMultiWindowModeChanged(
isInMultiWindowMode,
isDeferredStartup,
isFirstActivity(),
getActivityTab());
}
/**
* This method serves to distinguish windows in multi-window mode.
* @return True if this activity is the first created activity.
*/
protected boolean isFirstActivity() {
return true;
}
/** Handles back press events for Chrome in various states. */
protected final boolean handleOnBackPressed() {
RecordUserAction.record(
mNativeInitialized ? "SystemBack" : "SystemBackBeforeNativeInitialized");
if (isActivityFinishingOrDestroyed()) {
RecordUserAction.record("SystemBackOnActivityFinishingOrDestroyed");
}
if (TextBubble.getCountSupplier().get() != null
&& TextBubble.getCountSupplier().get() > 0) {
// TODO(crbug.com/1279941): should this stop propagating the event?
TextBubble.dismissBubbles();
BackPressManager.record(Type.TEXT_BUBBLE);
}
XrDelegate xrDelegate = XrDelegateProvider.getDelegate();
if (xrDelegate != null && xrDelegate.onBackPressed()) {
BackPressManager.record(Type.XR_DELEGATE);
return true;
}
if (mRootUiCoordinator.getBottomSheetController() != null
&& mRootUiCoordinator.getBottomSheetController().handleBackPress()) {
BackPressManager.record(BackPressHandler.Type.BOTTOM_SHEET);
return true;
}
if (mRootUiCoordinator.getPageInsightsBottomSheetController() != null
&& mRootUiCoordinator.getPageInsightsBottomSheetController().handleBackPress()) {
BackPressManager.record(BackPressHandler.Type.PAGE_INSIGHTS_BOTTOM_SHEET);
return true;
}
if (mCompositorViewHolderSupplier.hasValue()) {
LayoutManagerImpl layoutManager =
mCompositorViewHolderSupplier.get().getLayoutManager();
if (layoutManager != null && layoutManager.onBackPressed()) {
// Back press metrics recording is handled by LayoutManagerImpl internally.
return true;
}
}
// Fullscreen must be before selection popup. crbug.com/1454817.
if (exitFullscreenIfShowing()) {
BackPressManager.record(Type.FULLSCREEN);
return true;
}
SelectionPopupController controller = getSelectionPopupController();
if (controller != null && controller.isSelectActionBarShowing()) {
controller.clearSelection();
BackPressManager.record(Type.SELECTION_POPUP);
return true;
}
if (getManualFillingComponent().onBackPressed()) {
BackPressManager.record(Type.MANUAL_FILLING);
return true;
}
if (mRootUiCoordinator.getFindToolbarManager() != null
&& mRootUiCoordinator.getFindToolbarManager().isShowing()) {
BackPressManager.record(BackPressHandler.Type.FIND_TOOLBAR);
mRootUiCoordinator.getFindToolbarManager().hideToolbar();
return true;
}
return handleBackPressed();
}
private void initializeBackPressHandling() {
mBackPressManager.setIsGestureNavEnabledSupplier(
() -> UiUtils.isGestureNavigationMode(getWindow()));
mBackPressManager.setIsFirstVisibleContentDrawnSupplier(
() -> {
if (mActivityTabStartupMetricsTracker == null) return false;
return mActivityTabStartupMetricsTracker.isFirstVisibleContentRecorded();
});
final Runnable callbackForActivityTabStartupMetricsTracker =
() -> {
if (mActivityTabStartupMetricsTracker != null) {
mActivityTabStartupMetricsTracker.onBackPressed();
}
};
if (BackPressManager.isEnabled()) {
mBackPressManager.setOnBackPressedListener(callbackForActivityTabStartupMetricsTracker);
getOnBackPressedDispatcher().addCallback(this, mBackPressManager.getCallback());
// TODO(crbug.com/1279941): consider move to RootUiCoordinator.
mTextBubbleBackPressHandler = new TextBubbleBackPressHandler();
mBackPressManager.addHandler(mTextBubbleBackPressHandler, Type.TEXT_BUBBLE);
if (XrDelegateProvider.getDelegate() != null) {
mBackPressManager.addHandler(XrDelegateProvider.getDelegate(), Type.XR_DELEGATE);
}
mLayoutManagerSupplier.addObserver(
(layoutManager) -> {
assert !mBackPressManager.has(Type.SCENE_OVERLAY)
: "LayoutManager should be only set at most once";
mBackPressManager.addHandler(layoutManager, Type.SCENE_OVERLAY);
});
mSelectionPopupBackPressInitCallback =
(tabModelSelector) -> {
assert !mBackPressManager.has(Type.SELECTION_POPUP)
: "Tab Model Selector should be set at most once";
mSelectionPopupBackPressHandler =
new SelectionPopupBackPressHandler(tabModelSelector);
mBackPressManager.addHandler(
mSelectionPopupBackPressHandler, Type.SELECTION_POPUP);
getTabModelSelectorSupplier()
.removeObserver(mSelectionPopupBackPressInitCallback);
};
getTabModelSelectorSupplier().addObserver(mSelectionPopupBackPressInitCallback);
mBrowserControlsManagerSupplier.addObserver(
(controlManager) -> {
assert !mBackPressManager.has(Type.FULLSCREEN)
: "BrowserControlManager should be set at most once";
mBackPressManager.addHandler(
new FullscreenBackPressHandler(
controlManager.getFullscreenManager()),
BackPressHandler.Type.FULLSCREEN);
});
mCloseListenerManager = new CloseListenerManager(getActivityTabProvider());
mBackPressManager.addHandler(
mCloseListenerManager, BackPressHandler.Type.CLOSE_WATCHER);
} else {
OnBackPressedCallback callback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
mBackPressManager.recordSystemBackCountIfBeforeFirstVisibleContent();
callbackForActivityTabStartupMetricsTracker.run();
if (!ChromeActivity.this.handleOnBackPressed()) {
if (BackPressManager.shouldMoveToBackDuringStartup()) {
moveTaskToBack(true);
} else {
setEnabled(false);
getOnBackPressedDispatcher().onBackPressed();
setEnabled(true);
}
}
}
};
getOnBackPressedDispatcher().addCallback(this, callback);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (ChromeApplicationImpl.isSevereMemorySignal(level)) {
clearToolbarResourceCache();
}
}
private SelectionPopupController getSelectionPopupController() {
WebContents webContents = getCurrentWebContents();
return webContents != null ? SelectionPopupController.fromWebContents(webContents) : null;
}
@Override
public void createContextualSearchTab(String searchUrl) {
Tab currentTab = getActivityTab();
if (currentTab == null) return;
TabCreator tabCreator = getTabCreator(currentTab.isIncognito());
if (tabCreator == null) return;
tabCreator.createNewTab(
new LoadUrlParams(searchUrl, PageTransition.LINK),
TabLaunchType.FROM_LINK,
getActivityTab());
}
/** Opens the chrome://management page on a new tab. */
private void openChromeManagementPage() {
Tab currentTab = getActivityTab();
TabCreator tabCreator = getTabCreator(currentTab != null && currentTab.isIncognito());
if (tabCreator == null) return;
tabCreator.createNewTab(
new LoadUrlParams(UrlConstants.MANAGEMENT_URL, PageTransition.AUTO_TOPLEVEL),
TabLaunchType.FROM_CHROME_UI,
getActivityTab());
}
/**
* @return The {@link MenuOrKeyboardActionController} for registering menu or keyboard action
* handler for this activity.
*/
public MenuOrKeyboardActionController getMenuOrKeyboardActionController() {
return this;
}
@Override
public void registerMenuOrKeyboardActionHandler(MenuOrKeyboardActionHandler handler) {
mMenuActionHandlers.add(handler);
}
@Override
public void unregisterMenuOrKeyboardActionHandler(MenuOrKeyboardActionHandler handler) {
mMenuActionHandlers.remove(handler);
}
/**
* Handles menu item selection and keyboard shortcuts.
*
* @param id The ID of the selected menu item (defined in main_menu.xml) or keyboard shortcut
* (defined in values.xml).
* @param fromMenu Whether this was triggered from the menu.
* @return Whether the action was handled.
*/
@Override
public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) {
for (MenuOrKeyboardActionController.MenuOrKeyboardActionHandler handler :
mMenuActionHandlers) {
if (handler.handleMenuOrKeyboardAction(id, fromMenu)) return true;
}
@BrowserProfileType
int type = Profile.getBrowserProfileTypeFromProfile(getCurrentTabModel().getProfile());
if (id == R.id.preferences_id) {
SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
settingsLauncher.launchSettingsActivity(this);
RecordUserAction.record("MobileMenuSettings");
RecordHistogram.recordEnumeratedHistogram(
"Settings.OpenSettingsFromMenu.PerProfileType",
type,
BrowserProfileType.MAX_VALUE + 1);
return true;
}
if (id == R.id.update_menu_id) {
UpdateMenuItemHelper.getInstance(
getProfileProviderSupplier().get().getOriginalProfile())
.onMenuItemClicked(this);
return true;
}
final Tab currentTab = getActivityTab();
if (id == R.id.help_id) {
String url = currentTab != null ? currentTab.getUrl().getSpec() : "";
startHelpAndFeedback(
url,
"MobileMenuFeedback",
getTabModelSelector().getCurrentModel().getProfile());
return true;
}
if (id == R.id.open_history_menu_id) {
// 'currentTab' could only be null when opening history from start surface, which is
// not available on tablet.
assert (isTablet() && currentTab != null) || !isTablet();
if (currentTab != null && UrlUtilities.isNtpUrl(currentTab.getUrl())) {
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_HISTORY_MANAGER);
}
RecordUserAction.record("MobileMenuHistory");
HistoryManagerUtils.showHistoryManager(
this, currentTab, getTabModelSelector().isIncognitoSelected());
RecordHistogram.recordEnumeratedHistogram(
"Android.OpenHistoryFromMenu.PerProfileType",
type,
BrowserProfileType.MAX_VALUE + 1);
return true;
}
// All the code below assumes currentTab is not null, so return early if it is null.
if (currentTab == null) {
return false;
}
if (id == R.id.forward_menu_id) {
if (currentTab.canGoForward()) {
currentTab.goForward();
RecordUserAction.record("MobileMenuForward");
return true;
}
return false;
}
if (id == R.id.bookmark_this_page_id
|| id == R.id.add_bookmark_menu_id
|| id == R.id.edit_bookmark_menu_id) {
mTabBookmarkerSupplier.get().addOrEditBookmark(currentTab);
TrackerFactory.getTrackerForProfile(currentTab.getProfile())
.notifyEvent(EventConstants.APP_MENU_BOOKMARK_STAR_ICON_PRESSED);
RecordUserAction.record("MobileMenuAddToBookmarks");
return true;
}
if (id == R.id.enable_price_tracking_menu_id) {
mTabBookmarkerSupplier.get().startOrModifyPriceTracking(currentTab);
RecordUserAction.record("MobileMenuEnablePriceTracking");
TrackerFactory.getTrackerForProfile(currentTab.getProfile())
.notifyEvent(EventConstants.SHOPPING_LIST_PRICE_TRACK_FROM_MENU);
return true;
}
if (id == R.id.disable_price_tracking_menu_id) {
PowerBookmarkUtils.setPriceTrackingEnabledWithSnackbars(
mBookmarkModelSupplier.get(),
mBookmarkModelSupplier.get().getUserBookmarkIdForTab(currentTab),
/* enabled= */ false,
mSnackbarManager,
getResources(),
currentTab.getProfile(),
(success) -> {});
RecordUserAction.record("MobileMenuDisablePriceTracking");
return true;
}
if (id == R.id.offline_page_id) {
DownloadUtils.downloadOfflinePage(this, currentTab);
RecordUserAction.record("MobileMenuDownloadPage");
return true;
}
if (id == R.id.reload_menu_id) {
if (currentTab.isLoading()) {
currentTab.stopLoading();
RecordUserAction.record("MobileMenuStop");
} else {
currentTab.reload();
RecordUserAction.record("MobileMenuReload");
}
return true;
}
if (id == R.id.info_menu_id) {
ChromePageInfo pageInfo =
new ChromePageInfo(
getModalDialogManagerSupplier(),
null,
OpenedFromSource.MENU,
mRootUiCoordinator.getMerchantTrustSignalsCoordinatorSupplier()::get,
mRootUiCoordinator.getEphemeralTabCoordinatorSupplier(),
getTabCreator(currentTab.isIncognito()));
pageInfo.show(currentTab, ChromePageInfoHighlight.noHighlight());
return true;
}
if (id == R.id.translate_id) {
RecordUserAction.record("MobileMenuTranslate");
Tracker tracker = TrackerFactory.getTrackerForProfile(currentTab.getProfile());
tracker.notifyEvent(EventConstants.TRANSLATE_MENU_BUTTON_CLICKED);
TranslateBridge.translateTabWhenReady(currentTab);
return true;
}
if (id == R.id.readaloud_menu_id) {
RecordUserAction.record("MobileMenuReadAloud");
doReadCurrentTabAloud(currentTab);
return true;
}
if (id == R.id.print_id) {
RecordUserAction.record("MobileMenuPrint");
return doPrintShare(this, mActivityTabProvider);
}
if (id == R.id.add_to_homescreen_id) {
RecordUserAction.record("MobileMenuAddToHomescreen");
return doAddToHomescreenOrInstallWebApp(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_ADD_TO_HOMESCREEN);
}
if (id == R.id.install_webapp_id) {
RecordUserAction.record("InstallWebAppFromMenu");
return doAddToHomescreenOrInstallWebApp(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_INSTALL);
}
if (id == R.id.universal_install) {
RecordUserAction.record("UniversalInstallFromMenu");
return doUniversalInstall(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_UNIVERSAL_INSTALL);
}
if (id == R.id.open_webapk_id) {
RecordUserAction.record("MobileMenuOpenWebApk");
return doOpenWebApk(currentTab);
}
if (id == R.id.request_desktop_site_id || id == R.id.request_desktop_site_check_id) {
boolean usingDesktopUserAgent =
currentTab.getWebContents().getNavigationController().getUseDesktopUserAgent();
usingDesktopUserAgent = !usingDesktopUserAgent;
Profile profile = getCurrentTabModel().getProfile();
RequestDesktopUtils.setRequestDesktopSiteContentSettingsForUrl(
profile, currentTab.getUrl(), usingDesktopUserAgent);
// Use TabUtils.switchUserAgent() instead of Tab.reload(). Because we need to reload
// with LoadOriginalRequestURL. See http://crbug/1418587 for details.
TabUtils.switchUserAgent(
currentTab,
usingDesktopUserAgent,
/* forcedByUser= */ false,
UseDesktopUserAgentCaller.ON_MENU_OR_KEYBOARD_ACTION);
// TODO(crbug.com/1456560): Remove this IPH when the usage is low.
RequestDesktopUtils.maybeShowUserEducationPromptForAppMenuSelection(
profile, this, getModalDialogManager());
TrackerFactory.getTrackerForProfile(profile)
.notifyEvent(EventConstants.APP_MENU_DESKTOP_SITE_EXCEPTION_ADDED);
RequestDesktopUtils.recordUserChangeUserAgent(usingDesktopUserAgent, getActivityTab());
return true;
}
if (id == R.id.auto_dark_web_contents_id || id == R.id.auto_dark_web_contents_check_id) {
// Get values needed to check/enable auto dark for the current site.
Profile profile = getCurrentTabModel().getProfile();
GURL url = currentTab.getUrl();
// Flip auto dark state.
boolean isEnabled = WebContentsDarkModeController.isEnabledForUrl(profile, url);
WebContentsDarkModeController.setEnabledForUrl(profile, url, !isEnabled);
currentTab.getWebContents().notifyRendererPreferenceUpdate();
WebContentsDarkModeController.recordAutoDarkUkm(
currentTab.getWebContents(), !isEnabled);
// Show dialog informing user how to disable the feature globally and give feedback if
// disabling through the app menu for the nth time (determined by feature engagement).
if (isEnabled) {
WebContentsDarkModeMessageController.attemptToShowDialog(
this,
profile,
url.getSpec(),
getModalDialogManager(),
new SettingsLauncherImpl(),
HelpAndFeedbackLauncherImpl.getForProfile(profile));
}
return true;
}
if (id == R.id.reader_mode_prefs_id) {
DomDistillerUIUtils.openSettings(currentTab.getWebContents());
return true;
}
if (id == R.id.managed_by_menu_id) {
openChromeManagementPage();
return true;
}
return false;
}
/**
* Shows Help and Feedback and records the user action as well.
* @param url The URL of the tab the user is currently on.
* @param recordAction The user action to record.
* @param profile The current {@link Profile}.
*/
public void startHelpAndFeedback(String url, String recordAction, Profile profile) {
// Since reading back the compositor is asynchronous, we need to do the readback
// before starting the GoogleHelp.
String helpContextId =
HelpAndFeedbackLauncherImpl.getHelpContextIdFromUrl(
this, url, getCurrentTabModel().isIncognito());
HelpAndFeedbackLauncherImpl.getForProfile(profile).show(this, helpContextId, url);
RecordUserAction.record(recordAction);
}
private void markSessionResume() {
mUmaActivityObserver.startUmaSession(
getActivityType(), getTabModelSelector(), getWindowAndroid());
}
/** Mark that the UMA session has ended. */
private void markSessionEnd() {
mUmaActivityObserver.endUmaSession();
}
public final void postDeferredStartupIfNeeded() {
if (!mNativeInitialized) {
// Native hasn't loaded yet. Queue it up for later.
mDeferredStartupQueued = true;
return;
}
mDeferredStartupQueued = false;
if (!mDeferredStartupPosted) {
mDeferredStartupPosted = true;
onDeferredStartup();
}
}
@Override
public void terminateIncognitoSession() {}
@Override
public void onSceneChange(Layout layout) {}
@Override
public void onAttachFragment(Fragment fragment) {
if (mRootUiCoordinator == null) return;
mRootUiCoordinator.onAttachFragment(fragment);
}
/**
* Looks up the Chrome activity of the given web contents. This can be null. Should never be
* cached, because web contents can change activities, e.g., when user selects "Open in Chrome"
* menu item.
*
* @param webContents The web contents for which to lookup the Chrome activity.
* @return Possibly null Chrome activity that should never be cached.
* @deprecated Use {@link ActivityUtils#getActivityFromWebContents(WebContents)} instead.
*/
@Nullable
@Deprecated
public static ChromeActivity fromWebContents(@Nullable WebContents webContents) {
Activity activity = ActivityUtils.getActivityFromWebContents(webContents);
if (!(activity instanceof ChromeActivity)) return null;
return (ChromeActivity) activity;
}
private void setLowEndTheme() {
if (ActivityUtils.getThemeId() == R.style.Theme_Chromium_WithWindowAnimation_LowEnd) {
setTheme(R.style.Theme_Chromium_WithWindowAnimation_LowEnd);
}
}
/** Records histograms related to display dimensions. */
private void recordDisplayDimensions() {
DisplayAndroid display = DisplayAndroid.getNonMultiDisplay(this);
int displayWidth = DisplayUtil.pxToDp(display, display.getDisplayWidth());
int displayHeight = DisplayUtil.pxToDp(display, display.getDisplayHeight());
int smallestDisplaySize = Math.min(displayWidth, displayHeight);
int largestDisplaySize = Math.max(displayWidth, displayHeight);
// 10dp granularity.
RecordHistogram.recordLinearCountHistogram(
"Android.DeviceSize.SmallestDisplaySize2", smallestDisplaySize, 100, 1000, 92);
// 20dp granularity.
RecordHistogram.recordLinearCountHistogram(
"Android.DeviceSize.LargestDisplaySize2", largestDisplaySize, 200, 2000, 92);
double screenSizeInches = mRootUiCoordinator.getPrimaryDisplaySizeInInches();
// A sample value 10 times the screen size in inches will be used to support a granularity
// of 0.2" (or 2 units of the recorded value) for devices ranging from 4" to 15" (inclusive)
// in screen size. Two additional buckets will account for underflow and overflow screen
// sizes.
int sample = (int) (screenSizeInches * 10.0);
RecordHistogram.recordLinearCountHistogram(
"Android.DeviceSize.ScreenSizeInTensOfInches", sample, 40, 152, 58);
}
@Override
public boolean onActivityResultWithNative(int requestCode, int resultCode, Intent intent) {
if (super.onActivityResultWithNative(requestCode, resultCode, intent)) return true;
return false;
}
/**
* Called when VR mode is entered using this activity. 2D UI components that steal focus or
* draw over VR contents should be hidden in this call.
*/
public void onEnterVr() {}
/**
* Called when VR mode using this activity is exited. Any state set for VR should be restored
* in this call, including showing 2D UI that was hidden.
*/
public void onExitVr() {}
private void clearToolbarResourceCache() {
View v = findViewById(R.id.control_container);
try {
ControlContainer controlContainer = (ControlContainer) v;
if (controlContainer != null) {
controlContainer.getToolbarResourceAdapter().dropCachedBitmap();
}
} catch (ClassCastException e) {
// This is a workaround for crbug.com/1236981. Doing nothing here is better than
// crashing. We assert, which will be stripped in builds that get shipped to users.
Log.e(TAG, "crbug.com/1236981", e);
String extraInfo = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
extraInfo = " inflated from layout ID #" + v.getSourceLayoutResId();
}
assert false
: "View "
+ v.toString()
+ extraInfo
+ " was not a ControlContainer. "
+ " If you can reproduce, post in crbug.com/1236981";
}
}
/**
* TODO(https://crbug.com/931496): Revisit this as part of the broader discussion around
* activity-specific UI customizations.
* @return Whether this Activity supports the App Menu.
*/
public boolean supportsAppMenu() {
// Derived classes that disable the toolbar should also have the Menu disabled without
// having to explicitly disable the Menu as well.
return getToolbarLayoutId() != ActivityUtils.NO_RESOURCE_ID;
}
/**
* @return Whether this activity supports the find in page feature.
*/
public boolean supportsFindInPage() {
return true;
}
public RootUiCoordinator getRootUiCoordinatorForTesting() {
return mRootUiCoordinator;
}
// NightModeStateProvider.Observer implementation.
@Override
public void onNightModeStateChanged() {
// Note: order matters here because the call to super will recreate the activity.
// Note: it's possible for this method to be called before mNightModeReparentingController
// is constructed.
if (mTabReparentingControllerSupplier.get() != null) {
mTabReparentingControllerSupplier.get().prepareTabsForReparenting();
}
super.onNightModeStateChanged();
}
@VisibleForTesting
public TabletMode getTabletMode() {
assert mConfig != null
: "Can not determine the tablet mode when mConfig is not initialized";
int smallestWidth = DisplayUtil.getCurrentSmallestScreenWidth(this);
boolean isTablet = smallestWidth >= DeviceFormFactor.MINIMUM_TABLET_WIDTH_DP;
boolean wasTablet =
mConfig.smallestScreenWidthDp >= DeviceFormFactor.MINIMUM_TABLET_WIDTH_DP;
boolean didChangeTabletMode = wasTablet != isTablet;
if (didChangeTabletMode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.i(TAG, "Current smallest screen width is: " + smallestWidth);
}
return new TabletMode(isTablet, didChangeTabletMode);
}
/**
* Switch between phone and tablet mode and do the tab re-parenting in the meantime.
* Also update switch USE_MOBILE_UA depends on whether the device is tablet sized.
* @param isTablet whether the current screen is tablet size.
* @return whether screen layout change lead to a recreate.
*/
private boolean onScreenLayoutSizeChange(boolean isTablet) {
DeviceUtils.updateDeviceSpecificUserAgentSwitch(isTablet);
if (mTabReparentingControllerSupplier.get() != null && !mIsTabReparentingPrepared) {
mTabReparentingControllerSupplier.get().prepareTabsForReparenting();
mIsTabReparentingPrepared = true;
if (!isFinishing()) {
mIsRecreatingForTabletModeChange = true;
// Store the OnPause timestamp before recreation to capture unfold latency metric
// only if the activity is currently not in stopped state, to not capture the time
// when system was suspended. Hence, unfolding instances where Chrome wasn't in
// foreground are not captured in this metric.
if (isTablet
&& ApplicationStatus.getStateForActivity(this) != ActivityState.STOPPED) {
super.setOnPauseBeforeFoldRecreateTimestampMs();
}
recreate();
mHandler.removeCallbacks(mShowContentRunnable);
return true;
}
}
return false;
}
public @Nullable BookmarkModel getBookmarkModelForTesting() {
return mBookmarkModelSupplier.get();
}
public Configuration getSavedConfigurationForTesting() {
return mConfig;
}
public boolean deferredStartupPostedForTesting() {
return mDeferredStartupPosted;
}
public DisplayAndroidObserver getDisplayAndroidObserverForTesting() {
return mDisplayAndroidObserver;
}
public BackPressManager getBackPressManagerForTesting() {
return mBackPressManager;
}
public boolean recreatingForTabletModeChangeForTesting() {
return mIsRecreatingForTabletModeChange;
}
public ObservableSupplierImpl<EdgeToEdgeController>
getEdgeToEdgeControllerSupplierForTesting() {
return mEdgeToEdgeControllerSupplier;
}
/** Returns whether the print action was successfully started. */
private boolean doPrintShare(Activity activity, Supplier<Tab> currentTabSupplier) {
PrintingController printingController = PrintingControllerImpl.getInstance();
if (!currentTabSupplier.hasValue()) return false;
if (printingController == null || printingController.isBusy()) return false;
Tab currentTab = currentTabSupplier.get();
if (!UserPrefs.get(currentTab.getProfile()).getBoolean(Pref.PRINTING_ENABLED)) {
return false;
}
printingController.startPrint(
new TabPrinter(currentTab), new PrintManagerDelegateImpl(activity));
return true;
}
/** Returns a {@link CompositorViewHolder} instance for testing. */
public CompositorViewHolder getCompositorViewHolderForTesting() {
return mCompositorViewHolderSupplier.get();
}
private boolean doUniversalInstall(Tab currentTab, int menuItemType) {
BottomSheetController controller = BottomSheetControllerProvider.from(getWindowAndroid());
if (controller == null) {
// We have three options when this function fails. One is to abort the operation and do
// nothing (by returning false), or we can make one of the two options of the Universal
// Install dialog the default and go with that in case of errors. Since Install App is
// the menu item that would have been shown, if Universal Install was disabled, we
// fall back to the Install App option.
return doAddToHomescreenOrInstallWebApp(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_INSTALL);
}
ResolveInfo resolveInfo =
AppMenuPropertiesDelegateImpl.queryWebApkResolveInfo(this, currentTab);
boolean webAppInstalled =
resolveInfo != null && resolveInfo.activityInfo.packageName != null;
PwaUniversalInstallBottomSheetCoordinator pwaUniversalInstallBottomSheetCoordinator =
new PwaUniversalInstallBottomSheetCoordinator(
this,
currentTab.getWebContents(),
() -> {
doInstallThroughUniversalInstall(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_INSTALL);
},
() -> {
doInstallThroughUniversalInstall(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_ADD_TO_HOMESCREEN);
},
() -> {
doOpenWebApk(currentTab);
},
webAppInstalled,
controller,
R.drawable.outline_chevron_right_24dp,
R.drawable.down_arrow_on_circular_background,
R.drawable.chrome_logo_on_circular_background);
if (!pwaUniversalInstallBottomSheetCoordinator.show()) {
// Fall back to install method for the PWA.
return doAddToHomescreenOrInstallWebApp(
currentTab, AppMenuVerbiage.APP_MENU_OPTION_INSTALL);
}
return true;
}
/**
* Returns whether the Add to Home screen or Install Web App action was successfully started.
*/
private boolean doInstallThroughUniversalInstall(Tab currentTab, int menuItemType) {
return doAddToHomescreenOrInstallWebAppImpl(
currentTab, menuItemType, /* universalInstall= */ true);
}
/**
* Returns whether the Add to Home screen or Install Web App action was successfully started.
*/
private boolean doAddToHomescreenOrInstallWebApp(Tab currentTab, int menuItemType) {
return doAddToHomescreenOrInstallWebAppImpl(
currentTab, menuItemType, /* universalInstall= */ false);
}
private boolean doAddToHomescreenOrInstallWebAppImpl(
Tab currentTab, int menuItemType, boolean universalInstall) {
if (menuItemType == AppMenuVerbiage.APP_MENU_OPTION_INSTALL) {
PwaBottomSheetController controller =
PwaBottomSheetControllerProvider.from(getWindowAndroid());
if (controller != null
&& controller.requestOrExpandBottomSheetInstaller(
currentTab.getWebContents(), InstallTrigger.MENU)) {
return true;
}
}
AddToHomescreenCoordinator.showForAppMenu(
this,
getWindowAndroid(),
getModalDialogManager(),
currentTab.getWebContents(),
menuItemType,
universalInstall);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.ADD_TO_HOMESCREEN_IPH)) {
Tracker tracker = TrackerFactory.getTrackerForProfile(currentTab.getProfile());
tracker.notifyEvent(EventConstants.ADD_TO_HOMESCREEN_DIALOG_SHOWN);
}
return true;
}
/** Returns whether the Open WebAPK action was successfully started. */
private boolean doOpenWebApk(Tab currentTab) {
Context context = ContextUtils.getApplicationContext();
String packageName =
WebApkValidator.queryFirstWebApkPackage(context, currentTab.getUrl().getSpec());
Intent launchIntent =
WebApkNavigationClient.createLaunchWebApkIntent(
packageName, currentTab.getUrl().getSpec(), false);
try {
context.startActivity(launchIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.open_webapk_failed, Toast.LENGTH_SHORT).show();
}
return true;
}
private void doReadCurrentTabAloud(Tab currentTab) {
ReadAloudController readAloudController =
mRootUiCoordinator.getReadAloudControllerSupplier().get();
if (readAloudController != null) {
readAloudController.playTab(currentTab, ReadAloudController.Entrypoint.OVERFLOW_MENU);
}
}
/**
* Preserve whether the current screen is tablet size; and whether the tablet mode has changed.
*/
@VisibleForTesting
public static class TabletMode {
public boolean isTablet;
public boolean changed;
TabletMode(boolean isTablet, boolean changed) {
this.isTablet = isTablet;
this.changed = changed;
}
}
@Override
protected int getAutomotiveToolbarImplementation() {
return ChromeFeatureList.sVerticalAutomotiveBackButtonToolbar.isEnabled()
? AutomotiveToolbarImplementation.WITH_TOOLBAR_VIEW
: AutomotiveToolbarImplementation.WITH_ACTION_BAR;
}
private @Nullable SyncService getSyncServiceForOriginalProfile() {
if (!mTabModelProfileSupplier.hasValue()) return null;
return SyncServiceFactory.getForProfile(
mTabModelProfileSupplier.get().getOriginalProfile());
}
/**
* Returns the base view hosting Chrome that certain views (e.g. the omnibox suggestion list)
* will position themselves relative to. If null, the content view can be used.
*
* @return The base {@link View} hosting Chrome.
*/
protected @Nullable View getBaseChromeLayout() {
return mBaseChromeLayout;
}
}