| // 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.SearchManager; |
| import android.app.assist.AssistContent; |
| import android.content.ActivityNotFoundException; |
| import android.content.Context; |
| import android.content.Intent; |
| 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.CancellationSignal; |
| 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 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.BundleUtils; |
| 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.jank_tracker.DummyJankTracker; |
| import org.chromium.base.memory.MemoryPurgeManager; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.metrics.RecordUserAction; |
| 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.build.annotations.UsedByReflection; |
| 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.IntentHandler.IntentHandlerDelegate; |
| import org.chromium.chrome.browser.IntentHandler.TabOpenType; |
| 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.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.ContentOffsetProvider; |
| 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.CachedFeatureFlags; |
| 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.flags.IntCachedFieldTrialParameter; |
| 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.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.locale.LocaleManager; |
| 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.UmaSessionStats; |
| 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.Pref; |
| import org.chromium.chrome.browser.printing.TabPrinter; |
| import org.chromium.chrome.browser.profiles.Profile; |
| 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.SyncService; |
| import org.chromium.chrome.browser.tab.AccessibilityVisibilityHandler; |
| 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.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.LoadIfNeededCaller; |
| 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.TabModelSelectorObserver; |
| 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.TranslateAssistContent; |
| 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.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.chrome.browser.vr.VrModuleProvider; |
| import org.chromium.components.browser_ui.accessibility.FontSizePrefs; |
| import org.chromium.components.browser_ui.bottomsheet.BottomSheetController; |
| 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.InsetObserverView; |
| import org.chromium.components.browser_ui.widget.InsetObserverViewSupplier; |
| 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.ModelType; |
| import org.chromium.components.sync.PassphraseType; |
| 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.webxr.XrDelegate; |
| import org.chromium.components.webxr.XrDelegateProvider; |
| import org.chromium.content_public.browser.ContentFeatureList; |
| 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; |
| import java.util.function.Consumer; |
| |
| /** |
| * 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"; |
| public static final IntCachedFieldTrialParameter CONTENT_VIS_DELAY_MS = |
| new IntCachedFieldTrialParameter( |
| ChromeFeatureList.FOLDABLE_JANK_FIX, "content_visibility_delay", 5); |
| private C mComponent; |
| |
| /** 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 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 UmaSessionStats mUmaSessionStats; |
| private ContextReporter mContextReporter; |
| |
| private boolean mPartnerBrowserRefreshNeeded; |
| |
| protected final IntentHandler mIntentHandler; |
| |
| /** 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; |
| protected AccessibilityVisibilityHandler mAccessibilityVisibilityHandler; |
| |
| // 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<InsetObserverView> mInsetObserverViewSupplier = |
| new InsetObserverViewSupplier(); |
| 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 ActivityTabProvider mActivityTabProvider = new ActivityTabProvider(); |
| |
| /** Whether or not the activity is in started state. */ |
| private boolean mStarted; |
| |
| /** The data associated with the most recently selected menu item. */ |
| @Nullable |
| private Bundle mMenuItemData; |
| |
| /** |
| * 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 StylusWritingCoordinator mStylusWritingCoordinator; |
| private boolean mBlockingDrawForAppRestart; |
| private Runnable mShowContentRunnable; |
| |
| protected ChromeActivity() { |
| mIntentHandler = new IntentHandler(this, createIntentHandlerDelegate()); |
| mManualFillingComponentSupplier.set(ManualFillingComponentFactory.createComponent()); |
| } |
| |
| @Override |
| protected void onPreCreate() { |
| CachedFeatureFlags.onStartOrResumeCheckpoint(); |
| super.onPreCreate(); |
| initializeBackPressHandling(); |
| } |
| |
| @Override |
| protected void onAbortCreate() { |
| super.onAbortCreate(); |
| CachedFeatureFlags.onPauseCheckpoint(); |
| } |
| |
| @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(); |
| |
| // 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)); |
| } |
| |
| protected RootUiCoordinator createRootUiCoordinator() { |
| // TODO(https://crbug.com/931496): Remove dependency on ChromeActivity in favor of passing |
| // in direct dependencies on needed classes. While migrating code from Chrome*Activity |
| // to the RootUiCoordinator, passing the activity is an easy way to get access to a |
| // number of objects that will ultimately be owned by the RootUiCoordinator. This is not |
| // a recommended pattern. |
| // clang-format off |
| return new RootUiCoordinator(this, null, getShareDelegateSupplier(), |
| getActivityTabProvider(), mTabModelProfileSupplier, mBookmarkModelSupplier, |
| mTabBookmarkerSupplier, getContextualSearchManagerSupplier(), |
| getTabModelSelectorSupplier(), new OneshotSupplierImpl<>(), |
| new OneshotSupplierImpl<>(), new OneshotSupplierImpl<>(), |
| new OneshotSupplierImpl<>(), () -> null, mBrowserControlsManagerSupplier.get(), |
| getWindowAndroid(), new DummyJankTracker(), getLifecycleDispatcher(), |
| getLayoutManagerSupplier(), /* menuOrKeyboardActionController= */ this, |
| this::getActivityThemeColor, getModalDialogManagerSupplier(), |
| /* appMenuBlocker= */ this, this::supportsAppMenu, this::supportsFindInPage, |
| mTabCreatorManagerSupplier, getFullscreenManager(), mCompositorViewHolderSupplier, |
| getTabContentManagerSupplier(), this::getSnackbarManager, getActivityType(), |
| this::isInOverviewMode, this::isWarmOnResume, /* appMenuDelegate= */ this, |
| /* statusBarColorProvider= */ this, getIntentRequestTracker(), |
| mTabReparentingControllerSupplier, |
| /*ephemeralTabCoordinatorSupplier=*/new ObservableSupplierImpl<>(), |
| false, mBackPressManager); |
| // clang-format on |
| } |
| |
| 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, 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()) |
| : overridenCommonsFactory.create(this, mRootUiCoordinator::getBottomSheetController, |
| getTabModelSelectorSupplier(), getBrowserControlsManager(), |
| getBrowserControlsManager(), getBrowserControlsManager(), |
| getFullscreenManager(), getLayoutManagerSupplier(), |
| getLifecycleDispatcher(), this::getSnackbarManager, 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); |
| |
| // TODO(crbug.com/1199776): Move this to the RootUiCoordinator. |
| mSnackbarManager = new SnackbarManager(this, mBottomContainer, getWindowAndroid()); |
| 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, getContentOffsetProvider(), |
| !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); |
| mActivityTabStartupMetricsTracker = |
| new ActivityTabStartupMetricsTracker(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(); |
| |
| enableHardwareAcceleration(); |
| setLowEndTheme(); |
| |
| WarmupManager warmupManager = WarmupManager.getInstance(); |
| if (warmupManager.hasViewHierarchyWithToolbar(getControlContainerLayoutId())) { |
| 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)"); |
| 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 a custom view right after the root view 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(InsetObserverView.create(this)); |
| rootView.addView(mInsetObserverViewSupplier.get(), 0); |
| |
| 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(); |
| } |
| }; |
| |
| tabModelSelector.addObserver(new TabModelSelectorObserver() { |
| @Override |
| public void onTabStateInitialized() { |
| RequestDesktopUtils.maybeDowngradeSiteSettings(tabModelSelector); |
| tabModelSelector.removeObserver(this); |
| } |
| }); |
| } |
| |
| /** |
| * @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. |
| */ |
| @Nullable |
| public 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); |
| } |
| |
| /** |
| * @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(); |
| mCompositorViewHolderSupplier.get().onNativeLibraryReady( |
| getWindowAndroid(), getTabContentManager(), getPrefService()); |
| |
| // 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())); |
| } |
| |
| 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(); |
| } |
| } |
| |
| /** |
| * 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, LoadIfNeededCaller.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(LoadIfNeededCaller.ON_ACTIVITY_SHOWN); |
| } |
| } |
| VrModuleProvider.getDelegate().onActivityShown(this); |
| |
| MultiWindowUtils.getInstance().recordMultiWindowStateUkm(this, tab); |
| } |
| |
| private void onActivityHidden() { |
| VrModuleProvider.getDelegate().onActivityHidden(this); |
| |
| Tab tab = getActivityTab(); |
| TabModelSelector tabModelSelector = mTabModelOrchestrator.getTabModelSelector(); |
| if (tabModelSelector != null && !tabModelSelector.isReparentingInProgress() |
| && tab != null) { |
| tab.hide(TabHidingType.ACTIVITY_HIDDEN); |
| } |
| |
| if (mNativeInitialized |
| && ChromeFeatureList.isEnabled(ChromeFeatureList.KEEP_ANDROID_TINTED_RESOURCES) |
| && mCompositorViewHolderSupplier.hasValue()) { |
| LayoutManagerImpl layoutManager = |
| mCompositorViewHolderSupplier.get().getLayoutManager(); |
| if (layoutManager != null && layoutManager.getResourceManager() != null) { |
| layoutManager.getResourceManager().clearTintedResourceCache(); |
| } |
| } |
| } |
| |
| 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 = SyncService.get(); |
| |
| 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; |
| } else { |
| reportSyncStatus(syncService); |
| } |
| |
| if (mSyncStateChangedListener == null && syncService != null) { |
| mSyncStateChangedListener = () -> createContextReporterIfNeeded(); |
| syncService.addSyncStateChangedListener(mSyncStateChangedListener); |
| } |
| } |
| |
| /** Records an appropriate status via UMA given the current sync status. */ |
| private static void reportSyncStatus(@Nullable SyncService syncService) { |
| if (syncService == null || !syncService.isEngineInitialized()) { |
| ContextReporter.reportStatus(ContextReporter.STATUS_SYNC_NOT_INITIALIZED); |
| } else if (!syncService.getActiveDataTypes().contains(ModelType.TYPED_URLS) |
| && !syncService.getActiveDataTypes().contains(ModelType.HISTORY)) { |
| ContextReporter.reportStatus(ContextReporter.STATUS_SYNC_NOT_SYNCING_URLS); |
| } else if (syncService.getPassphraseType() != PassphraseType.KEYSTORE_PASSPHRASE |
| && syncService.getPassphraseType() != PassphraseType.TRUSTED_VAULT_PASSPHRASE) { |
| ContextReporter.reportStatus(ContextReporter.STATUS_SYNC_NOT_KEYSTORE_PASSPHRASE); |
| } else { |
| ContextReporter.reportStatus(ContextReporter.STATUS_SYNC_OTHER); |
| } |
| } |
| |
| @Override |
| public void onResumeWithNative() { |
| // First, update the activity type in order to have it properly captured in |
| // markSessionResume; stage the activity type value such that it can be picked up when |
| // the new UMA record is opened as a part of the subsequent session resume. |
| ChromeSessionState.setActivityType(getActivityType()); |
| |
| // 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().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(); |
| } |
| |
| 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 (inPicture) { |
| ensureFullscreenVideoPictureInPictureController(); |
| mFullscreenVideoPictureInPictureController.onEnteredPictureInPictureMode(); |
| mLastPictureInPictureModeForTesting = true; |
| } else if (mFullscreenVideoPictureInPictureController != null) { |
| mLastPictureInPictureModeForTesting = false; |
| mFullscreenVideoPictureInPictureController.onFrameworkExitedPictureInPicture(); |
| } |
| } |
| |
| /** |
| * 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 = SyncService.get(); |
| 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(); |
| if (mIntentHandler.shouldIgnoreIntent(intent, /*startedActivity=*/false, isCustomTab())) { |
| return; |
| } |
| |
| mIntentHandler.onNewIntent(intent); |
| } |
| |
| /** |
| * @return The type for this activity. |
| */ |
| @ActivityType |
| public abstract 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 = IntentHandler.getTimestampFromIntent(getIntent()); |
| if (intentTimestamp != -1) { |
| recordIntentToCreationTime(getOnCreateTimestampMs() - intentTimestamp); |
| } |
| |
| recordDisplayDimensions(); |
| int playServicesVersion = PlayServicesVersionInfo.getApkVersionNumber(); |
| RecordHistogram.recordBooleanHistogram( |
| "Android.PlayServices.Installed", playServicesVersion > 0); |
| RecordHistogram.recordSparseHistogram( |
| "Android.PlayServices.Version", playServicesVersion); |
| |
| FontSizePrefs.getInstance(Profile.getLastUsedRegularProfile()) |
| .recordUserFontPrefOnStartup(); |
| }); |
| |
| DeferredStartupHandler.getInstance().addDeferredTask(() -> { |
| if (isActivityFinishingOrDestroyed()) return; |
| ForcedSigninProcessor.checkCanSignIn(ChromeActivity.this); |
| }); |
| |
| // GSA connection is not needed on low-end devices because Icing is disabled. |
| if (!SysUtils.isLowEndDevice()) { |
| DeferredStartupHandler.getInstance().addDeferredTask(() -> { |
| if (isActivityFinishingOrDestroyed()) return; |
| if (!GSAState.getInstance().isGsaAvailable()) { |
| ContextReporter.reportStatus(ContextReporter.STATUS_GSA_NOT_AVAILABLE); |
| 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; |
| } |
| |
| /** |
| * 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(); |
| boolean inOverviewMode = isInOverviewMode(); |
| |
| // Attempt to fetch translate data here so we can record UMA even if it won't be attached. |
| @Nullable |
| String structuredData = TranslateAssistContent.getTranslateDataForTab(tab, inOverviewMode); |
| |
| // No information is provided in incognito mode and overview mode. |
| if (tab != null && !tab.isIncognito() && !inOverviewMode) { |
| outContent.setWebUri(Uri.parse(tab.getUrl().getSpec())); |
| if (structuredData != null) { |
| outContent.setStructuredData(structuredData); |
| } |
| } |
| } |
| |
| // TODO(crbug.com/973781): Once Chromium is built against Android Q SDK, replace |
| // @SuppressWarnings with @Override |
| @SuppressWarnings("MissingOverride") |
| @RequiresApi(29) |
| @UsedByReflection("Called from Android Q") |
| public void onPerformDirectAction(String actionId, Bundle arguments, |
| CancellationSignal cancellationSignal, Consumer<Bundle> callback) { |
| mRootUiCoordinator.onPerformDirectAction(actionId, arguments, cancellationSignal, callback); |
| } |
| |
| // TODO(crbug.com/973781): Once Chromium is built against Android Q SDK: |
| // - replace @SuppressWarnings with @Override |
| // - replace Consumer with Consumer<List<DirectAction>> |
| @SuppressWarnings("MissingOverride") |
| @RequiresApi(29) |
| @UsedByReflection("Called from Android Q") |
| public void onGetDirectActions(CancellationSignal cancellationSignal, Consumer callback) { |
| mRootUiCoordinator.onGetDirectActions(cancellationSignal, callback); |
| } |
| |
| @Override |
| public long getOnCreateTimestampMs() { |
| return super.getOnCreateTimestampMs(); |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| } |
| |
| /** |
| * 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 (mStylusWritingCoordinator != null) { |
| mStylusWritingCoordinator.destroy(); |
| mStylusWritingCoordinator = null; |
| } |
| |
| 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() { |
| return new ModalDialogManager( |
| new AppModalPresenter(this), ModalDialogManager.ModalDialogType.APP); |
| } |
| |
| protected Drawable getBackgroundDrawable() { |
| 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()); |
| VrModuleProvider.maybeInit(); |
| VrModuleProvider.getDelegate().onNativeLibraryAvailable(); |
| |
| PowerMonitor.create(); |
| |
| // The first launch of the screenshot feature benefits from this DFM being installed |
| // proactively. However without the isolated split feature there are performance regressions |
| // as a result of adding this extra code. |
| if (BundleUtils.isolatedSplitsEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O |
| && AppHooks.get().getImageEditorModuleProvider() != null) { |
| AppHooks.get().getImageEditorModuleProvider().maybeInstallModuleDeferred(); |
| } |
| |
| super.finishNativeInitialization(); |
| |
| mManualFillingComponentSupplier.get().initialize(getWindowAndroid(), |
| mRootUiCoordinator.getBottomSheetController(), |
| (ChromeKeyboardVisibilityDelegate) getWindowAndroid().getKeyboardDelegate(), |
| mBackPressManager, 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 (ChromeFeatureList.isEnabled(ChromeFeatureList.FOLDABLE_JANK_FIX) |
| && !mBlockingDrawForAppRestart && didChangeTabletMode()) { |
| mBlockingDrawForAppRestart = true; |
| findViewById(android.R.id.content).setVisibility(View.INVISIBLE); |
| showContent(); |
| } |
| maybeOnScreenSizeChange(); |
| } |
| }; |
| display.addObserver(mDisplayAndroidObserver); |
| } |
| |
| private boolean maybeOnScreenSizeChange() { |
| if (didChangeTabletMode()) { |
| return onScreenLayoutSizeChange(); |
| } |
| 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) { |
| mMenuItemData = 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() { |
| 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; |
| } |
| |
| protected IntentHandlerDelegate createIntentHandlerDelegate() { |
| return new IntentHandlerDelegate() { |
| @Override |
| public void processWebSearchIntent(String query) { |
| final Intent searchIntent = new Intent(Intent.ACTION_WEB_SEARCH); |
| searchIntent.putExtra(SearchManager.QUERY, query); |
| Callback<Boolean> callback = result -> { |
| if (result != null && result) startActivity(searchIntent); |
| }; |
| LocaleManager.getInstance().showSearchEnginePromoIfNeeded( |
| ChromeActivity.this, callback); |
| } |
| |
| @Override |
| public long getIntentHandlingTimeMs() { |
| return 0; |
| } |
| |
| @Override |
| public void processTranslateTabIntent( |
| @Nullable String targetLanguageCode, @Nullable String expectedUrl) {} |
| |
| @Override |
| public void processUrlViewIntent(LoadUrlParams loadUrlParams, |
| @TabOpenType int tabOpenType, String externalAppId, int tabIdToBringToFront, |
| Intent intent) {} |
| }; |
| } |
| |
| /** |
| * @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; |
| } |
| |
| @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(); |
| 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. |
| */ |
| @NonNull |
| public FullscreenManager getFullscreenManager() { |
| return getBrowserControlsManager().getFullscreenManager(); |
| } |
| |
| /** |
| * @return The content offset provider, may be null. |
| */ |
| public ContentOffsetProvider getContentOffsetProvider() { |
| return mCompositorViewHolderSupplier.get(); |
| } |
| |
| /** |
| * @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().getSupplierForBottomInset()); |
| 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()) { |
| mContextualSearchManagerSupplier.get().initialize(contentContainer, 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.getValue()); |
| } |
| |
| // 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)); |
| } |
| } |
| |
| VrModuleProvider.getDelegate().onMultiWindowModeChanged(isInMultiWindowMode); |
| |
| 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); |
| } |
| |
| if (VrModuleProvider.getDelegate().onBackPressed()) { |
| BackPressManager.record(Type.VR_DELEGATE); |
| return true; |
| } |
| |
| XrDelegate xrDelegate = XrDelegateProvider.getDelegate(); |
| if (xrDelegate != null && xrDelegate.onBackPressed()) { |
| BackPressManager.record(Type.XR_DELEGATE); |
| 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; |
| } |
| } |
| |
| 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 (exitFullscreenIfShowing()) { |
| BackPressManager.record(Type.FULLSCREEN); |
| return true; |
| } |
| |
| if (mRootUiCoordinator.getBottomSheetController() != null |
| && mRootUiCoordinator.getBottomSheetController().handleBackPress()) { |
| BackPressManager.record(BackPressHandler.Type.BOTTOM_SHEET); |
| return true; |
| } |
| |
| // This only intercepts back press when back press refactor is disabled on T+. Otherwise, |
| // back press is intercepted in FindToolbarManager internally. |
| if (BuildInfo.isAtLeastT() && mRootUiCoordinator.getFindToolbarManager() != null |
| && mRootUiCoordinator.getFindToolbarManager().isShowing()) { |
| BackPressManager.record(BackPressHandler.Type.FIND_TOOLBAR); |
| mRootUiCoordinator.getFindToolbarManager().hideToolbar(); |
| return true; |
| } |
| |
| return handleBackPressed(); |
| } |
| |
| @CallSuper |
| protected void initializeBackPressHandling() { |
| if (BackPressManager.isEnabled()) { |
| getOnBackPressedDispatcher().addCallback(this, mBackPressManager.getCallback()); |
| // TODO(crbug.com/1279941): consider move to RootUiCoordinator. |
| mTextBubbleBackPressHandler = new TextBubbleBackPressHandler(); |
| mBackPressManager.addHandler(mTextBubbleBackPressHandler, Type.TEXT_BUBBLE); |
| mBackPressManager.addHandler(VrModuleProvider.getDelegate(), Type.VR_DELEGATE); |
| |
| 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); |
| }); |
| } else { |
| OnBackPressedCallback callback = new OnBackPressedCallback(true) { |
| @Override |
| public void handleOnBackPressed() { |
| if (!ChromeActivity.this.handleOnBackPressed()) { |
| 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().onMenuItemClicked(this); |
| return true; |
| } |
| |
| final Tab currentTab = getActivityTab(); |
| |
| if (id == R.id.help_id) { |
| String url = currentTab != null ? currentTab.getUrl().getSpec() : ""; |
| Profile profile = getTabModelSelector().isIncognitoSelected() |
| ? Profile.getLastUsedRegularProfile().getPrimaryOTRProfile( |
| /*createIfNeeded=*/true) |
| : Profile.getLastUsedRegularProfile(); |
| startHelpAndFeedback(url, "MobileMenuFeedback", profile); |
| 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(Profile.getLastUsedRegularProfile()) |
| .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(Profile.getLastUsedRegularProfile()) |
| .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(), (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()); |
| pageInfo.show(currentTab, ChromePageInfoHighlight.noHighlight()); |
| return true; |
| } |
| |
| if (id == R.id.translate_id) { |
| RecordUserAction.record("MobileMenuTranslate"); |
| Tracker tracker = TrackerFactory.getTrackerForProfile( |
| Profile.fromWebContents(currentTab.getWebContents())); |
| tracker.notifyEvent(EventConstants.TRANSLATE_MENU_BUTTON_CLICKED); |
| TranslateBridge.translateTabWhenReady(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); |
| } |
| |
| if (id == R.id.install_webapp_id) { |
| RecordUserAction.record("InstallWebAppFromMenu"); |
| return doAddToHomescreenOrInstallWebApp(currentTab); |
| } |
| |
| 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; |
| if (ContentFeatureList.isEnabled(ContentFeatureList.REQUEST_DESKTOP_SITE_EXCEPTIONS)) { |
| Profile profile = getCurrentTabModel().getProfile(); |
| RequestDesktopUtils.setRequestDesktopSiteContentSettingsForUrl( |
| profile, currentTab.getUrl(), usingDesktopUserAgent); |
| // Use TabUtils.switchUserAgent() instead of Tab.reload(). Because we need to reload |
| // with ReloadType::ORIGINAL_REQUEST_URL. See http://crbug/1418587 for details. |
| TabUtils.switchUserAgent(currentTab, usingDesktopUserAgent, |
| /* forcedByUser */ false, |
| UseDesktopUserAgentCaller.ON_MENU_OR_KEYBOARD_ACTION); |
| RequestDesktopUtils.maybeShowUserEducationPromptForAppMenuSelection( |
| profile, this, getModalDialogManager()); |
| TrackerFactory.getTrackerForProfile(profile).notifyEvent( |
| EventConstants.APP_MENU_DESKTOP_SITE_EXCEPTION_ADDED); |
| } else { |
| TabUtils.switchUserAgent(currentTab, usingDesktopUserAgent, /* forcedByUser */ true, |
| UseDesktopUserAgentCaller.ON_MENU_OR_KEYBOARD_ACTION); |
| TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile()) |
| .notifyEvent(EventConstants.APP_MENU_DESKTOP_SITE_FOR_TAB_CLICKED); |
| } |
| 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() { |
| // Start new session for UMA. |
| if (mUmaSessionStats == null) { |
| mUmaSessionStats = new UmaSessionStats(this); |
| } |
| |
| UmaSessionStats.updateMetricsServiceState(); |
| mUmaSessionStats.startNewSession(getTabModelSelector(), getWindowAndroid()); |
| } |
| |
| /** |
| * Mark that the UMA session has ended. |
| */ |
| private void markSessionEnd() { |
| if (mUmaSessionStats == null) { |
| // If you hit this assert, please update crbug.com/172653 on how you got there. |
| assert false; |
| return; |
| } |
| // Record session metrics. |
| mUmaSessionStats.logAndEndSession(); |
| } |
| |
| 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 onTabSelectionHinted(int tabId) {} |
| |
| @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; |
| if (VrModuleProvider.getDelegate().onActivityResultWithNative(requestCode, resultCode)) { |
| 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; |
| } |
| |
| @VisibleForTesting |
| 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 boolean didChangeTabletMode() { |
| assert mConfig |
| != null : "Can not determine the tablet mode when mConfig is not initialized"; |
| int smallestWidth = 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 didChangeTabletMode; |
| } |
| |
| /** |
| * Switch between phone and tablet mode and do the tab re-parenting in the meantime. |
| * @return whether screen layout change lead to a recreate. |
| */ |
| private boolean onScreenLayoutSizeChange() { |
| if (mTabReparentingControllerSupplier.get() != null && !mIsTabReparentingPrepared) { |
| mTabReparentingControllerSupplier.get().prepareTabsForReparenting(); |
| mIsTabReparentingPrepared = true; |
| if (!isFinishing()) { |
| recreate(); |
| mHandler.removeCallbacks(mShowContentRunnable); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @VisibleForTesting |
| @Nullable |
| public BookmarkModel getBookmarkModelForTesting() { |
| return mBookmarkModelSupplier.get(); |
| } |
| |
| @VisibleForTesting |
| public Configuration getSavedConfigurationForTesting() { |
| return mConfig; |
| } |
| |
| @VisibleForTesting |
| public boolean deferredStartupPostedForTesting() { |
| return mDeferredStartupPosted; |
| } |
| |
| @VisibleForTesting |
| public DisplayAndroidObserver getDisplayAndroidObserverForTesting() { |
| return mDisplayAndroidObserver; |
| } |
| |
| @VisibleForTesting |
| public BackPressManager getBackPressManagerForTesting() { |
| return mBackPressManager; |
| } |
| |
| /** 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; |
| if (!UserPrefs.get(Profile.getLastUsedRegularProfile()).getBoolean(Pref.PRINTING_ENABLED)) { |
| return false; |
| } |
| printingController.startPrint( |
| new TabPrinter(currentTabSupplier.get()), new PrintManagerDelegateImpl(activity)); |
| return true; |
| } |
| |
| /** |
| * Returns a {@link CompositorViewHolder} instance for testing. |
| */ |
| public CompositorViewHolder getCompositorViewHolderForTesting() { |
| return mCompositorViewHolderSupplier.get(); |
| } |
| |
| private static PrefService getPrefService() { |
| return UserPrefs.get(Profile.getLastUsedRegularProfile()); |
| } |
| |
| /** |
| * Returns whether the Add to Home screen or Install Web App action was successfully started. |
| */ |
| private boolean doAddToHomescreenOrInstallWebApp(Tab currentTab) { |
| PwaBottomSheetController controller = |
| PwaBottomSheetControllerProvider.from(getWindowAndroid()); |
| if (controller != null |
| && controller.requestOrExpandBottomSheetInstaller( |
| currentTab.getWebContents(), InstallTrigger.MENU)) { |
| return true; |
| } |
| AddToHomescreenCoordinator.showForAppMenu(this, getWindowAndroid(), getModalDialogManager(), |
| currentTab.getWebContents(), mMenuItemData); |
| if (ChromeFeatureList.isEnabled(ChromeFeatureList.ADD_TO_HOMESCREEN_IPH)) { |
| Tracker tracker = TrackerFactory.getTrackerForProfile( |
| Profile.fromWebContents(currentTab.getWebContents())); |
| 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; |
| } |
| } |