blob: 12a2f8dc94511ca98863b81363c17d285095b99f [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.SearchManager;
import android.app.assist.AssistContent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v7.app.AlertDialog;
import android.util.DisplayMetrics;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import org.chromium.base.ActivityState;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BaseSwitches;
import org.chromium.base.CommandLine;
import org.chromium.base.SysUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.IntentHandler.IntentHandlerDelegate;
import org.chromium.chrome.browser.IntentHandler.TabOpenType;
import org.chromium.chrome.browser.appmenu.AppMenu;
import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerDocument;
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.contextualsearch.ContextualSearchFieldTrial;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager.ContextualSearchTabPromotionDelegate;
import org.chromium.chrome.browser.datausage.DataUseTabUIManager;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.dom_distiller.DistilledPagePrefsView;
import org.chromium.chrome.browser.dom_distiller.ReaderModeManager;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.gsa.ContextReporter;
import org.chromium.chrome.browser.gsa.GSAServiceClient;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.init.AsyncInitializationActivity;
import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.metrics.UmaSessionStats;
import org.chromium.chrome.browser.nfc.BeamController;
import org.chromium.chrome.browser.nfc.BeamProvider;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.pageinfo.WebsiteSettingsPopup;
import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.PreferencesLauncher;
import org.chromium.chrome.browser.printing.TabPrinter;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.chrome.browser.snackbar.DataUseSnackbarController;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarManageable;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.chrome.browser.sync.SyncController;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
import org.chromium.chrome.browser.tabmodel.EmptyTabModel;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tabmodel.TabWindowManager;
import org.chromium.chrome.browser.toolbar.Toolbar;
import org.chromium.chrome.browser.toolbar.ToolbarControlContainer;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.webapps.AddToHomescreenDialog;
import org.chromium.chrome.browser.widget.ControlContainer;
import org.chromium.content.browser.ContentVideoView;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.common.ContentSwitches;
import org.chromium.content_public.browser.ContentBitmapCallback;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.readback_types.ReadbackResponse;
import org.chromium.policy.CombinedPolicyProvider.PolicyChangeListener;
import org.chromium.printing.PrintManagerDelegateImpl;
import org.chromium.printing.PrintingController;
import org.chromium.ui.base.ActivityWindowAndroid;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
/**
* A {@link AsyncInitializationActivity} that builds and manages a {@link CompositorViewHolder}
* and associated classes.
*/
public abstract class ChromeActivity extends AsyncInitializationActivity
implements TabCreatorManager, AccessibilityStateChangeListener, PolicyChangeListener,
ContextualSearchTabPromotionDelegate, SnackbarManageable, SceneChangeObserver {
/**
* Factory which creates the AppMenuHandler.
*/
public interface AppMenuHandlerFactory {
/**
* @return AppMenuHandler for the given activity and menu resource id.
*/
public AppMenuHandler get(Activity activity, AppMenuPropertiesDelegate delegate,
int menuResourceId);
}
/**
* No control container to inflate during initialization.
*/
static final int NO_CONTROL_CONTAINER = -1;
/** Prevents race conditions when deleting snapshot database. */
private static final Object SNAPSHOT_DATABASE_LOCK = new Object();
private static final String SNAPSHOT_DATABASE_REMOVED = "snapshot_database_removed";
private static final String SNAPSHOT_DATABASE_NAME = "snapshots.db";
/** Delay in ms after first page load finishes before we initiate deferred startup actions. */
private static final int DEFERRED_STARTUP_DELAY_MS = 1000;
/**
* Timeout in ms for reading PartnerBrowserCustomizations provider.
*/
private static final int PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS = 10000;
private static final String TAG = "ChromeActivity";
private static final Rect EMPTY_RECT = new Rect();
private TabModelSelector mTabModelSelector;
private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
private TabCreatorManager.TabCreator mRegularTabCreator;
private TabCreatorManager.TabCreator mIncognitoTabCreator;
private TabContentManager mTabContentManager;
private UmaSessionStats mUmaSessionStats;
private ContextReporter mContextReporter;
protected GSAServiceClient mGSAServiceClient;
private boolean mPartnerBrowserRefreshNeeded;
protected IntentHandler mIntentHandler;
/** Whether onDeferredStartup() has been run. */
private boolean mDeferredStartupNotified;
// The class cannot implement TouchExplorationStateChangeListener,
// because it is only available for Build.VERSION_CODES.KITKAT and later.
// We have to instantiate the TouchExplorationStateChangeListner object in the code.
@SuppressLint("NewApi")
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
// Observes when sync becomes ready to create the mContextReporter.
private ProfileSyncService.SyncStateChangedListener mSyncStateChangedListener;
private ActivityWindowAndroid mWindowAndroid;
private ChromeFullscreenManager mFullscreenManager;
private CompositorViewHolder mCompositorViewHolder;
private InsetObserverView mInsetObserverView;
private ContextualSearchManager mContextualSearchManager;
private ReaderModeManager mReaderModeManager;
private SnackbarManager mSnackbarManager;
private DataUseSnackbarController mDataUseSnackbarController;
private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuHandler mAppMenuHandler;
private ToolbarManager mToolbarManager;
private BookmarkModelObserver mBookmarkObserver;
// Time in ms that it took took us to inflate the initial layout
private long mInflateInitialLayoutDurationMs;
private AssistStatusHandler mAssistStatusHandler;
private static AppMenuHandlerFactory sAppMenuHandlerFactory = new AppMenuHandlerFactory() {
@Override
public AppMenuHandler get(
Activity activity, AppMenuPropertiesDelegate delegate, int menuResourceId) {
return new AppMenuHandler(activity, delegate, menuResourceId);
}
};
// See enableHardwareAcceleration()
private boolean mSetWindowHWA;
/**
* @param The {@link AppMenuHandlerFactory} for creating {@link mAppMenuHandler}
*/
@VisibleForTesting
public static void setAppMenuHandlerFactoryForTesting(AppMenuHandlerFactory factory) {
sAppMenuHandlerFactory = factory;
}
@Override
public void preInflationStartup() {
super.preInflationStartup();
// 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.isInitialized();
ApplicationInitialization.enableFullscreenFlags(
getResources(), this, getControlContainerHeightResource());
// TODO(twellington): Remove this work around when the underlying bug is fixed.
// See crbug.com/583099.
if (!Build.VERSION.CODENAME.equals("N")) {
getWindow().setBackgroundDrawable(getBackgroundDrawable());
}
mWindowAndroid = ((ChromeApplication) getApplicationContext())
.createActivityWindowAndroid(this);
mWindowAndroid.restoreInstanceState(getSavedInstanceState());
}
@SuppressLint("NewApi")
@Override
public void postInflationStartup() {
super.postInflationStartup();
mSnackbarManager = new SnackbarManager((ViewGroup) findViewById(android.R.id.content));
mDataUseSnackbarController = new DataUseSnackbarController(this, getSnackbarManager());
mAssistStatusHandler = createAssistStatusHandler();
if (mAssistStatusHandler != null) {
if (mTabModelSelector != null) {
mAssistStatusHandler.setTabModelSelector(mTabModelSelector);
}
mAssistStatusHandler.updateAssistState();
}
// If a user had ALLOW_LOW_END_DEVICE_UI explicitly set to false then we manually override
// SysUtils.isLowEndDevice() with a switch so that they continue to see the normal UI. This
// is only the case for grandfathered-in svelte users. We no longer do so for newer users.
if (!ChromePreferenceManager.getInstance(this).getAllowLowEndDeviceUi()) {
CommandLine.getInstance().appendSwitch(
BaseSwitches.DISABLE_LOW_END_DEVICE_MODE);
}
AccessibilityManager manager = (AccessibilityManager)
getBaseContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.addAccessibilityStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mTouchExplorationStateChangeListener = new TouchExplorationStateChangeListener() {
@Override
public void onTouchExplorationStateChanged(boolean enabled) {
checkAccessibility();
}
};
manager.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
}
// Make the activity listen to policy change events
getChromeApplication().addPolicyChangeListener(this);
// Set up the animation placeholder to be the SurfaceView. This disables the
// SurfaceView's 'hole' clipping during animations that are notified to the window.
mWindowAndroid.setAnimationPlaceholderView(mCompositorViewHolder.getSurfaceView());
// Inform the WindowAndroid of the keyboard accessory view.
mWindowAndroid.setKeyboardAccessoryView((ViewGroup) findViewById(R.id.keyboard_accessory));
initializeToolbar();
}
@Override
protected View getViewToBeDrawnBeforeInitializingNative() {
View controlContainer = findViewById(R.id.control_container);
return controlContainer != null ? controlContainer
: super.getViewToBeDrawnBeforeInitializingNative();
}
/**
* This function builds the {@link CompositorViewHolder}. Subclasses *must* call
* super.setContentView() before using {@link #getTabModelSelector()} or
* {@link #getCompositorViewHolder()}.
*/
@Override
protected final void setContentView() {
final long begin = SystemClock.elapsedRealtime();
TraceEvent.begin("onCreate->setContentView()");
enableHardwareAcceleration();
setLowEndTheme();
int controlContainerLayoutId = getControlContainerLayoutId();
WarmupManager warmupManager = WarmupManager.getInstance();
if (warmupManager.hasBuiltOrClearViewHierarchyWithToolbar(controlContainerLayoutId)) {
View placeHolderView = new View(this);
setContentView(placeHolderView);
ViewGroup contentParent = (ViewGroup) placeHolderView.getParent();
WarmupManager.getInstance().transferViewHierarchyTo(contentParent);
contentParent.removeView(placeHolderView);
} else {
setContentView(R.layout.main);
if (controlContainerLayoutId != NO_CONTROL_CONTAINER) {
ViewStub toolbarContainerStub =
((ViewStub) findViewById(R.id.control_container_stub));
toolbarContainerStub.setLayoutResource(controlContainerLayoutId);
toolbarContainerStub.inflate();
}
}
TraceEvent.end("onCreate->setContentView()");
mInflateInitialLayoutDurationMs = SystemClock.elapsedRealtime() - begin;
// Set the status bar color to black by default. This is an optimization for
// Chrome not to draw under status and navigation bars when we use the default
// black status bar
ApiCompatibilityUtils.setStatusBarColor(getWindow(), Color.BLACK);
ViewGroup rootView = (ViewGroup) getWindow().getDecorView().getRootView();
mCompositorViewHolder = (CompositorViewHolder) findViewById(R.id.compositor_view_holder);
mCompositorViewHolder.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.
// ContentViewCore needs the insets to determine the portion of the screen obscured by
// non-content displaying things such as the OSK.
mInsetObserverView = InsetObserverView.create(this);
rootView.addView(mInsetObserverView, 0);
}
/**
* Constructs {@link ToolbarManager} and the handler necessary for controlling the menu on the
* {@link Toolbar}. Extending classes can override this call to avoid creating the toolbar.
*/
protected void initializeToolbar() {
final View controlContainer = findViewById(R.id.control_container);
assert controlContainer != null;
ToolbarControlContainer toolbarContainer = (ToolbarControlContainer) controlContainer;
mAppMenuPropertiesDelegate = createAppMenuPropertiesDelegate();
mAppMenuHandler = sAppMenuHandlerFactory.get(this, mAppMenuPropertiesDelegate,
getAppMenuLayoutId());
mToolbarManager = new ToolbarManager(this, toolbarContainer, mAppMenuHandler,
mAppMenuPropertiesDelegate, getCompositorViewHolder().getInvalidator());
mAppMenuHandler.addObserver(new AppMenuObserver() {
@Override
public void onMenuVisibilityChanged(boolean isVisible) {
if (isVisible && !isInOverviewMode()) {
// The app menu badge should be removed the first time the menu is opened.
if (mToolbarManager.getToolbar().isShowingAppMenuUpdateBadge()) {
mToolbarManager.getToolbar().removeAppMenuUpdateBadge(true);
mCompositorViewHolder.requestRender();
}
}
if (!isVisible) {
mAppMenuPropertiesDelegate.onMenuDismissed();
MenuItem updateMenuItem = mAppMenuHandler.getAppMenu().getMenu().findItem(
R.id.update_menu_id);
if (updateMenuItem != null && updateMenuItem.isVisible()) {
UpdateMenuItemHelper.getInstance().onMenuDismissed();
}
}
}
});
}
/**
* @return {@link ToolbarManager} that belongs to this activity.
*/
protected ToolbarManager getToolbarManager() {
return mToolbarManager;
}
/**
* @return The resource id for the menu to use in {@link AppMenu}. Default is R.menu.main_menu.
*/
protected int getAppMenuLayoutId() {
return R.menu.main_menu;
}
/**
* @return {@link AppMenuPropertiesDelegate} instance that the {@link AppMenuHandler}
* should be using in this activity.
*/
protected AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new AppMenuPropertiesDelegate(this);
}
/**
* @return The assist handler for this activity.
*/
protected AssistStatusHandler getAssistStatusHandler() {
return mAssistStatusHandler;
}
/**
* @return A newly constructed assist handler for this given activity type.
*/
protected AssistStatusHandler createAssistStatusHandler() {
return new AssistStatusHandler(this);
}
/**
* @return The resource id for the layout to use for {@link ControlContainer}. 0 by default.
*/
protected int getControlContainerLayoutId() {
return NO_CONTROL_CONTAINER;
}
/**
* @return Whether contextual search is allowed for this activity or not.
*/
protected boolean isContextualSearchAllowed() {
return true;
}
@Override
public void initializeState() {
super.initializeState();
IntentHandler.setTestIntentsEnabled(
CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_TEST_INTENTS));
mIntentHandler = new IntentHandler(createIntentHandlerDelegate(), getPackageName());
}
@Override
public void initializeCompositor() {
TraceEvent.begin("ChromeActivity:CompositorInitialization");
super.initializeCompositor();
setTabContentManager(new TabContentManager(this, getContentOffsetProvider(),
DeviceClassManager.enableSnapshots()));
mCompositorViewHolder.onNativeLibraryReady(mWindowAndroid, getTabContentManager());
if (isContextualSearchAllowed() && ContextualSearchFieldTrial.isEnabled(this)) {
mContextualSearchManager = new ContextualSearchManager(this, mWindowAndroid, this);
}
if (ReaderModeManager.isEnabled(this)) {
mReaderModeManager = new ReaderModeManager(getTabModelSelector(), this);
if (mToolbarManager != null) {
mToolbarManager.addFindToolbarObserver(
mReaderModeManager.getFindToolbarObserver());
}
}
TraceEvent.end("ChromeActivity:CompositorInitialization");
}
/**
* Sets the {@link TabModelSelector} owned by this {@link ChromeActivity}.
* @param tabModelSelector A {@link TabModelSelector} instance.
*/
protected void setTabModelSelector(TabModelSelector tabModelSelector) {
mTabModelSelector = tabModelSelector;
if (mTabModelSelectorTabObserver != null) mTabModelSelectorTabObserver.destroy();
mTabModelSelectorTabObserver = new TabModelSelectorTabObserver(tabModelSelector) {
@Override
public void didFirstVisuallyNonEmptyPaint(Tab tab) {
if (DataUseTabUIManager.checkAndResetDataUseTrackingStarted(tab)) {
mDataUseSnackbarController.showDataUseTrackingStartedBar();
} else if (DataUseTabUIManager.shouldShowDataUseEndedSnackbar(
getApplicationContext())
&& DataUseTabUIManager.checkAndResetDataUseTrackingEnded(tab)) {
mDataUseSnackbarController.showDataUseTrackingEndedBar();
}
}
@Override
public void onShown(Tab tab) {
setStatusBarColor(tab, tab.getThemeColor());
}
@Override
public void onHidden(Tab tab) {
mDataUseSnackbarController.dismissDataUseBar();
}
@Override
public void onDestroyed(Tab tab) {
mDataUseSnackbarController.dismissDataUseBar();
}
@Override
public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
postDeferredStartupIfNeeded();
}
@Override
public void onPageLoadFinished(Tab tab) {
postDeferredStartupIfNeeded();
OfflinePageUtils.showOfflineSnackbarIfNecessary(ChromeActivity.this, tab);
}
@Override
public void onCrash(Tab tab, boolean sadTabShown) {
postDeferredStartupIfNeeded();
}
@Override
public void onDidChangeThemeColor(Tab tab, int color) {
if (getActivityTab() != tab) return;
setStatusBarColor(tab, color);
if (getToolbarManager() == null) return;
getToolbarManager().updatePrimaryColor(color);
ControlContainer controlContainer =
(ControlContainer) findViewById(R.id.control_container);
controlContainer.getToolbarResourceAdapter().invalidate(null);
}
};
if (mAssistStatusHandler != null) {
mAssistStatusHandler.setTabModelSelector(tabModelSelector);
}
}
@Override
public void onStartWithNative() {
super.onStartWithNative();
UpdateMenuItemHelper.getInstance().onStart();
getChromeApplication().onStartWithNative();
FeatureUtilities.setDocumentModeEnabled(FeatureUtilities.isDocumentMode(this));
WarmupManager.getInstance().clearWebContentsIfNecessary();
if (GSAState.getInstance(this).isGsaAvailable()) {
mGSAServiceClient = new GSAServiceClient(this);
mGSAServiceClient.connect();
createContextReporterIfNeeded();
} else {
ContextReporter.reportStatus(ContextReporter.STATUS_GSA_NOT_AVAILABLE);
}
mCompositorViewHolder.resetFlags();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Tab tab = getActivityTab();
if (tab == null) return;
if (hasFocus) {
tab.onActivityShown();
} else {
boolean stopped = ApplicationStatus.getStateForActivity(this) == ActivityState.STOPPED;
if (stopped) tab.onActivityHidden();
}
}
/**
* Set device status bar to a given color.
* @param tab The tab that is currently showing.
* @param color The color that the status bar should be set to.
*/
protected void setStatusBarColor(Tab tab, int color) {
int statusBarColor = (tab != null && tab.isDefaultThemeColor())
? Color.BLACK : ColorUtils.getDarkenedColorForStatusBar(color);
ApiCompatibilityUtils.setStatusBarColor(getWindow(), statusBarColor);
}
private void createContextReporterIfNeeded() {
if (mContextReporter != null || getActivityTab() == null) return;
final SyncController syncController = SyncController.get(this);
final ProfileSyncService syncService = ProfileSyncService.get();
if (syncController != null && syncController.isSyncingUrlsWithKeystorePassphrase()) {
assert syncService != null;
mContextReporter = ((ChromeApplication) getApplicationContext()).createGsaHelper()
.getContextReporter(this);
if (mSyncStateChangedListener != null) {
syncService.removeSyncStateChangedListener(mSyncStateChangedListener);
mSyncStateChangedListener = null;
}
return;
} else {
ContextReporter.reportSyncStatus(syncService);
}
if (mSyncStateChangedListener == null && syncService != null) {
mSyncStateChangedListener = new ProfileSyncService.SyncStateChangedListener() {
@Override
public void syncStateChanged() {
createContextReporterIfNeeded();
}
};
syncService.addSyncStateChangedListener(mSyncStateChangedListener);
}
}
@Override
public void onResumeWithNative() {
super.onResumeWithNative();
markSessionResume();
if (getActivityTab() != null) {
LaunchMetrics.commitLaunchMetrics(getActivityTab().getWebContents());
}
FeatureUtilities.setCustomTabVisible(isCustomTab());
}
@Override
public void onPauseWithNative() {
markSessionEnd();
super.onPauseWithNative();
}
@Override
public void onStopWithNative() {
Tab tab = getActivityTab();
if (tab != null && !hasWindowFocus()) tab.onActivityHidden();
if (mAppMenuHandler != null) mAppMenuHandler.hideAppMenu();
if (mGSAServiceClient != null) {
mGSAServiceClient.disconnect();
mGSAServiceClient = null;
if (mSyncStateChangedListener != null) {
ProfileSyncService syncService = ProfileSyncService.get();
if (syncService != null) {
syncService.removeSyncStateChangedListener(mSyncStateChangedListener);
}
mSyncStateChangedListener = null;
}
}
super.onStopWithNative();
}
@Override
public void onNewIntentWithNative(Intent intent) {
super.onNewIntentWithNative(intent);
if (mIntentHandler.shouldIgnoreIntent(this, intent)) return;
mIntentHandler.onNewIntent(this, intent);
}
/**
* @return Whether the given activity contains a CustomTab.
*/
public boolean isCustomTab() {
return false;
}
@Override
protected void onDeferredStartup() {
super.onDeferredStartup();
boolean crashDumpUploadingDisabled =
CommandLine.getInstance().hasSwitch(ChromeSwitches.DISABLE_CRASH_DUMP_UPLOAD);
DeferredStartupHandler.getInstance()
.onDeferredStartup(getChromeApplication(), crashDumpUploadingDisabled);
BeamController.registerForBeam(this, new BeamProvider() {
@Override
public String getTabUrlForBeam() {
if (isOverlayVisible()) return null;
if (getActivityTab() == null) return null;
return getActivityTab().getUrl();
}
});
UpdateMenuItemHelper.getInstance().checkForUpdateOnBackgroundThread(this);
removeSnapshotDatabase();
if (mToolbarManager != null) {
String simpleName = getClass().getSimpleName();
RecordHistogram.recordTimesHistogram("MobileStartup.ToolbarInflationTime." + simpleName,
mInflateInitialLayoutDurationMs, TimeUnit.MILLISECONDS);
mToolbarManager.onDeferredStartup(getOnCreateTimestampMs(), simpleName);
}
recordKeyboardLocaleUma();
}
@Override
public void onStart() {
if (AsyncTabParamsManager.hasParamsWithTabToReparent()) {
mCompositorViewHolder.prepareForTabReparenting();
}
super.onStart();
if (mContextReporter != null) mContextReporter.enable();
if (mPartnerBrowserRefreshNeeded) {
mPartnerBrowserRefreshNeeded = false;
PartnerBrowserCustomizations.initializeAsync(getApplicationContext(),
PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS);
PartnerBrowserCustomizations.setOnInitializeAsyncFinished(new Runnable() {
@Override
public void run() {
if (PartnerBrowserCustomizations.isIncognitoDisabled()) {
terminateIncognitoSession();
}
}
});
}
if (mCompositorViewHolder != null) mCompositorViewHolder.onStart();
mSnackbarManager.onStart();
// Explicitly call checkAccessibility() so things are initialized correctly when Chrome has
// been re-started after closing due to the last tab being closed when homepage is enabled.
// See crbug.com/541546.
checkAccessibility();
}
@Override
public void onStop() {
super.onStop();
if (mContextReporter != null) mContextReporter.disable();
// We want to refresh partner browser provider every onStart().
mPartnerBrowserRefreshNeeded = true;
if (mCompositorViewHolder != null) mCompositorViewHolder.onStop();
mSnackbarManager.onStop();
}
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onProvideAssistContent(AssistContent outContent) {
if (getAssistStatusHandler() == null || !getAssistStatusHandler().isAssistSupported()) {
// No information is provided in incognito mode.
return;
}
Tab tab = getActivityTab();
if (tab != null && !isInOverviewMode()) {
outContent.setWebUri(Uri.parse(tab.getUrl()));
}
}
@Override
public long getOnCreateTimestampMs() {
return super.getOnCreateTimestampMs();
}
/**
* 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 (mReaderModeManager != null) {
mReaderModeManager.destroy();
mReaderModeManager = null;
}
if (mContextualSearchManager != null) {
mContextualSearchManager.destroy();
mContextualSearchManager = null;
}
if (mTabModelSelectorTabObserver != null) {
mTabModelSelectorTabObserver.destroy();
mTabModelSelectorTabObserver = null;
}
if (mCompositorViewHolder != null) {
if (mCompositorViewHolder.getLayoutManager() != null) {
mCompositorViewHolder.getLayoutManager().removeSceneChangeObserver(this);
}
mCompositorViewHolder.shutDown();
mCompositorViewHolder = null;
}
onDestroyInternal();
if (mToolbarManager != null) {
mToolbarManager.destroy();
mToolbarManager = null;
}
TabModelSelector selector = getTabModelSelector();
if (selector != null) selector.destroy();
if (mWindowAndroid != null) {
mWindowAndroid.destroy();
mWindowAndroid = null;
}
getChromeApplication().removePolicyChangeListener(this);
if (mTabContentManager != null) {
mTabContentManager.destroy();
mTabContentManager = null;
}
AccessibilityManager manager = (AccessibilityManager)
getBaseContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.removeAccessibilityStateChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
manager.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener);
}
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() {
}
/**
* This will handle passing {@link Intent} results back to the {@link WindowAndroid}. It will
* return whether or not the {@link WindowAndroid} has consumed the event or not.
*/
@Override
public boolean onActivityResultWithNative(int requestCode, int resultCode, Intent intent) {
if (super.onActivityResultWithNative(requestCode, resultCode, intent)) return true;
return mWindowAndroid.onActivityResult(requestCode, resultCode, intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (mWindowAndroid != null) {
if (mWindowAndroid.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
return;
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mWindowAndroid.saveInstanceState(outState);
}
/**
* @return The unified manager for all snackbar related operations.
*/
@Override
public SnackbarManager getSnackbarManager() {
return mSnackbarManager;
}
protected Drawable getBackgroundDrawable() {
return new ColorDrawable(
ApiCompatibilityUtils.getColor(getResources(), R.color.light_background_color));
}
/**
* Called when the accessibility status of this device changes. This might be triggered by
* touch exploration or general accessibility status updates. It is an aggregate of two other
* accessibility update methods.
* @see #onAccessibilityModeChanged(boolean)
* @see #onTouchExplorationStateChanged(boolean)
* @param enabled Whether or not accessibility and touch exploration are currently enabled.
*/
protected void onAccessibilityModeChanged(boolean enabled) {
InfoBarContainer.setIsAllowedToAutoHide(!enabled);
if (mToolbarManager != null) mToolbarManager.onAccessibilityStatusChanged(enabled);
if (mContextualSearchManager != null) {
mContextualSearchManager.onAccessibilityModeChanged(enabled);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item != null && onMenuOrKeyboardAction(item.getItemId(), true)) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Triggered when the share menu item is selected.
* This creates and shows a share intent picker dialog or starts a share intent directly.
* @param shareDirectly Whether it should share directly with the activity that was most
* recently used to share.
* @param isIncognito Whether currentTab is incognito.
*/
public void onShareMenuItemSelected(final boolean shareDirectly, boolean isIncognito) {
final Tab currentTab = getActivityTab();
if (currentTab == null) return;
final Activity mainActivity = this;
ContentBitmapCallback callback = new ContentBitmapCallback() {
@Override
public void onFinishGetBitmap(Bitmap bitmap, int response) {
// Check whether this page is an offline page, and use its online URL if so.
String url = currentTab.getOfflinePageOriginalUrl();
RecordHistogram.recordBooleanHistogram(
"OfflinePages.SharedPageWasOffline", url != null);
// If there is no entry in the offline pages DB for this tab, use the tab's
// URL directly.
if (url == null) url = currentTab.getUrl();
ShareHelper.share(
shareDirectly, mainActivity, currentTab.getTitle(), url, bitmap);
if (shareDirectly) {
RecordUserAction.record("MobileMenuDirectShare");
} else {
RecordUserAction.record("MobileMenuShare");
}
}
};
if (isIncognito || currentTab.getWebContents() == null) {
callback.onFinishGetBitmap(null, ReadbackResponse.SURFACE_UNAVAILABLE);
} else {
currentTab.getWebContents().getContentBitmapAsync(
Bitmap.Config.ARGB_8888, 1.f, EMPTY_RECT, callback);
}
}
/**
* @return Whether the activity is in overview mode.
*/
public boolean isInOverviewMode() {
return false;
}
/**
* @return Whether the app menu should be shown.
*/
public boolean shouldShowAppMenu() {
// Do not show the menu if Contextual Search or Reader Mode panel is opened.
// TODO(mdjones): This could potentially be handled by the OverlayPanelManager or as
// an event if the panels were SceneOverlays.
if ((mContextualSearchManager != null && mContextualSearchManager.isSearchPanelOpened())
|| (mReaderModeManager != null && mReaderModeManager.isPanelOpened())) {
return false;
}
return true;
}
/**
* Shows the app menu (if possible) for a key press on the keyboard with the correct anchor view
* chosen depending on device configuration and the visible menu button to the user.
*/
protected void showAppMenuForKeyboardEvent() {
if (getAppMenuHandler() == null) return;
boolean hasPermanentMenuKey = ViewConfiguration.get(this).hasPermanentMenuKey();
getAppMenuHandler().showAppMenu(
hasPermanentMenuKey ? null : getToolbarManager().getMenuAnchor(), false);
}
/**
* Allows Activities that extend ChromeActivity to do additional hiding/showing of menu items.
* @param menu Menu that is going to be shown when the menu button is pressed.
*/
public void prepareMenu(Menu menu) {
}
protected IntentHandlerDelegate createIntentHandlerDelegate() {
return new IntentHandlerDelegate() {
@Override
public void processWebSearchIntent(String query) {
Intent searchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
searchIntent.putExtra(SearchManager.QUERY, query);
startActivity(searchIntent);
}
@Override
public void processUrlViewIntent(String url, String referer, String headers,
TabOpenType tabOpenType, String externalAppId, int tabIdToBringToFront,
boolean hasUserGesture, Intent intent) {
}
};
}
/**
* @return The resource id that contains how large the top controls are.
*/
public int getControlContainerHeightResource() {
return R.dimen.control_container_height;
}
@Override
public final void onAccessibilityStateChanged(boolean enabled) {
checkAccessibility();
}
private void checkAccessibility() {
onAccessibilityModeChanged(DeviceClassManager.isAccessibilityModeEnabled(this));
}
/**
* @return A casted version of {@link #getApplication()}.
*/
public ChromeApplication getChromeApplication() {
return (ChromeApplication) getApplication();
}
/**
* Add the specified tab to bookmarks or allows to edit the bookmark if the specified tab is
* already bookmarked. If a new bookmark is added, a snackbar will be shown.
* @param tabToBookmark The tab that needs to be bookmarked.
*/
public void addOrEditBookmark(final Tab tabToBookmark) {
if (tabToBookmark == null || tabToBookmark.isFrozen()) {
return;
}
// Defense in depth against the UI being erroneously enabled.
if (!mToolbarManager.getBookmarkBridge().isEditBookmarksEnabled()) {
assert false;
return;
}
// Note the use of getUserBookmarkId() over getBookmarkId() here: Managed bookmarks can't be
// edited. If the current URL is only bookmarked by managed bookmarks, this will return
// INVALID_BOOKMARK_ID, so the code below will fall back on adding a new bookmark instead.
// TODO(bauerb): This does not take partner bookmarks into account.
final long bookmarkId = tabToBookmark.getUserBookmarkId();
final BookmarkModel bookmarkModel = new BookmarkModel();
bookmarkModel.runAfterBookmarkModelLoaded(new Runnable() {
@Override
public void run() {
// Gives up the bookmarking if the tab is being destroyed.
if (!tabToBookmark.isClosing() && tabToBookmark.isInitialized()) {
BookmarkUtils.addOrEditBookmark(bookmarkId, bookmarkModel,
tabToBookmark, getSnackbarManager(), ChromeActivity.this);
}
}
});
}
/**
* {@link TabModelSelector} no longer implements TabModel. Use getTabModelSelector() or
* getCurrentTabModel() depending on your needs.
* @return The {@link TabModelSelector}, possibly null.
*/
public TabModelSelector getTabModelSelector() {
return mTabModelSelector;
}
/**
* Returns the {@link InsetObserverView} that has the current system window
* insets information.
* @return The {@link InsetObserverView}, possibly null.
*/
public InsetObserverView getInsetObserverView() {
return mInsetObserverView;
}
@Override
public TabCreatorManager.TabCreator getTabCreator(boolean incognito) {
return incognito ? mIncognitoTabCreator : mRegularTabCreator;
}
/**
* Sets the {@link ChromeTabCreator}s owned by this {@link ChromeActivity}.
* @param regularTabCreator A {@link ChromeTabCreator} instance.
*/
public void setTabCreators(TabCreatorManager.TabCreator regularTabCreator,
TabCreatorManager.TabCreator incognitoTabCreator) {
mRegularTabCreator = regularTabCreator;
mIncognitoTabCreator = incognitoTabCreator;
}
/**
* Convenience method that returns a tab creator for the currently selected {@link TabModel}.
* @return A tab creator for the currently selected {@link TabModel}.
*/
public TabCreatorManager.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.
*/
public TabContentManager getTabContentManager() {
return mTabContentManager;
}
/**
* Sets the {@link TabContentManager} owned by this {@link ChromeActivity}.
* @param tabContentManager A {@link TabContentManager} instance.
*/
protected void setTabContentManager(TabContentManager tabContentManager) {
mTabContentManager = tabContentManager;
}
/**
* 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();
}
/**
* 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. FullScreenActivity and 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() {
return TabModelUtils.getCurrentTab(getCurrentTabModel());
}
/**
* @return The current ContentViewCore, or null if the tab does not exist or is not showing a
* ContentViewCore.
*/
public ContentViewCore getCurrentContentViewCore() {
return TabModelUtils.getCurrentContentViewCore(getCurrentTabModel());
}
/**
* @return A {@link WindowAndroid} instance.
*/
public WindowAndroid getWindowAndroid() {
return mWindowAndroid;
}
/**
* @return A {@link CompositorViewHolder} instance.
*/
public CompositorViewHolder getCompositorViewHolder() {
return mCompositorViewHolder;
}
/**
* Gets the full screen manager.
* @return The fullscreen manager, possibly null
*/
public ChromeFullscreenManager getFullscreenManager() {
return mFullscreenManager;
}
/**
* @return The content offset provider, may be null.
*/
public ContentOffsetProvider getContentOffsetProvider() {
return mCompositorViewHolder.getContentOffsetProvider();
}
/**
* @return The {@code ContextualSearchManager} or {@code null} if none;
*/
public ContextualSearchManager getContextualSearchManager() {
return mContextualSearchManager;
}
/**
* @return The {@code ReaderModeManager} or {@code null} if none;
*/
public ReaderModeManager getReaderModeManager() {
return mReaderModeManager;
}
/**
* Create a full-screen manager to be used by this activity.
* @param controlContainer The control container that will be controlled by the full-screen
* manager.
* @return A {@link ChromeFullscreenManager} instance that's been created.
*/
protected ChromeFullscreenManager createFullscreenManager(View controlContainer) {
return new ChromeFullscreenManager(this, controlContainer, getTabModelSelector(),
getControlContainerHeightResource(), true);
}
/**
* Exits the fullscreen mode, if any. Does nothing if no fullscreen is present.
* @return Whether the fullscreen mode is currently showing.
*/
protected boolean exitFullscreenIfShowing() {
ContentVideoView view = ContentVideoView.getContentVideoView();
if (view != null && view.getContext() == this) {
view.exitFullscreen(false);
return true;
}
if (getFullscreenManager().getPersistentFullscreenMode()) {
getFullscreenManager().setPersistentFullscreenMode(false);
return true;
}
return false;
}
/**
* Initializes the {@link CompositorViewHolder} with the relevant content it needs to properly
* show content on the screen.
* @param layoutManager A {@link LayoutManagerDocument} instance. This class is
* responsible for driving all high level screen content and
* determines which {@link Layout} is shown when.
* @param urlBar The {@link View} representing the URL bar (must be
* focusable) or {@code null} if none exists.
* @param contentContainer A {@link ViewGroup} that can have content attached by
* {@link Layout}s.
* @param controlContainer A {@link ControlContainer} instance to draw.
*/
protected void initializeCompositorContent(
LayoutManagerDocument layoutManager, View urlBar, ViewGroup contentContainer,
ControlContainer controlContainer) {
if (controlContainer != null) {
mFullscreenManager = createFullscreenManager((View) controlContainer);
}
if (mContextualSearchManager != null) {
mContextualSearchManager.initialize(contentContainer);
mContextualSearchManager.setSearchContentViewDelegate(layoutManager);
}
layoutManager.addSceneChangeObserver(this);
mCompositorViewHolder.setLayoutManager(layoutManager);
mCompositorViewHolder.setFocusable(false);
mCompositorViewHolder.setControlContainer(controlContainer);
mCompositorViewHolder.setFullscreenHandler(mFullscreenManager);
mCompositorViewHolder.setUrlBar(urlBar);
mCompositorViewHolder.onFinishNativeInitialization(getTabModelSelector(), this,
getTabContentManager(), contentContainer, mContextualSearchManager,
mReaderModeManager);
if (controlContainer != null
&& DeviceClassManager.enableToolbarSwipe(FeatureUtilities.isDocumentMode(this))) {
controlContainer.setSwipeHandler(
getCompositorViewHolder().getLayoutManager().getTopSwipeHandler());
}
}
/**
* Called when the back button is pressed.
* @return Whether or not the back button was handled.
*/
protected abstract boolean handleBackPressed();
@Override
public void onOrientationChange(int orientation) {
// TODO(mdjones): Orientation change for panels should not be handled here. The event
// should probably be passed to the OverlayPanelManager.
if (mContextualSearchManager != null) mContextualSearchManager.onOrientationChange();
if (mReaderModeManager != null) mReaderModeManager.onOrientationChange();
if (mToolbarManager != null) mToolbarManager.onOrientationChange();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mAppMenuHandler != null) mAppMenuHandler.hideAppMenu();
super.onConfigurationChanged(newConfig);
}
@Override
public final void onBackPressed() {
if (mCompositorViewHolder != null) {
LayoutManager layoutManager = mCompositorViewHolder.getLayoutManager();
boolean layoutConsumed = layoutManager != null && layoutManager.onBackPressed();
if (layoutConsumed || mContextualSearchManager != null
&& mContextualSearchManager.onBackPressed()) {
RecordUserAction.record("SystemBack");
return;
}
}
if (!isSelectActionBarShowing() && handleBackPressed()) {
return;
}
// This will close the select action bar if it is showing, otherwise close the activity.
super.onBackPressed();
}
private boolean isSelectActionBarShowing() {
Tab tab = getActivityTab();
if (tab == null) return false;
ContentViewCore contentViewCore = tab.getContentViewCore();
if (contentViewCore == null) return false;
return contentViewCore.isSelectActionBarShowing();
}
@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),
TabModel.TabLaunchType.FROM_LINK, getActivityTab());
}
/**
* @return The {@link AppMenuHandler} associated with this activity.
*/
@VisibleForTesting
public AppMenuHandler getAppMenuHandler() {
return mAppMenuHandler;
}
/**
* @return The {@link AppMenuPropertiesDelegate} associated with this activity.
*/
@VisibleForTesting
public AppMenuPropertiesDelegate getAppMenuPropertiesDelegate() {
return mAppMenuPropertiesDelegate;
}
/**
* Callback after UpdateMenuItemHelper#checkForUpdateOnBackgroundThread is complete.
* @param updateAvailable Whether an update is available.
*/
public void onCheckForUpdate(boolean updateAvailable) {
if (UpdateMenuItemHelper.getInstance().shouldShowToolbarBadge(this)) {
mToolbarManager.getToolbar().showAppMenuUpdateBadge();
mCompositorViewHolder.requestRender();
} else {
mToolbarManager.getToolbar().removeAppMenuUpdateBadge(false);
}
}
/**
* 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.
*/
public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) {
if (id == R.id.preferences_id) {
PreferencesLauncher.launchSettingsPage(this, null);
RecordUserAction.record("MobileMenuSettings");
} else if (id == R.id.show_menu) {
showAppMenuForKeyboardEvent();
}
if (id == R.id.update_menu_id) {
UpdateMenuItemHelper.getInstance().onMenuItemClicked(this);
return true;
}
// All the code below assumes currentTab is not null, so return early if it is null.
final Tab currentTab = getActivityTab();
if (currentTab == null) {
return false;
} else if (id == R.id.forward_menu_id) {
if (currentTab.canGoForward()) {
currentTab.goForward();
RecordUserAction.record("MobileMenuForward");
RecordUserAction.record("MobileTabClobbered");
}
} else if (id == R.id.bookmark_this_page_id) {
addOrEditBookmark(currentTab);
RecordUserAction.record("MobileMenuAddToBookmarks");
} else if (id == R.id.reload_menu_id) {
if (currentTab.isLoading()) {
currentTab.stopLoading();
} else {
currentTab.reload();
RecordUserAction.record("MobileToolbarReload");
}
} else if (id == R.id.info_menu_id) {
WebsiteSettingsPopup.show(this, currentTab);
} else if (id == R.id.open_history_menu_id) {
currentTab.loadUrl(
new LoadUrlParams(UrlConstants.HISTORY_URL, PageTransition.AUTO_TOPLEVEL));
RecordUserAction.record("MobileMenuHistory");
StartupMetrics.getInstance().recordOpenedHistory();
} else if (id == R.id.share_menu_id || id == R.id.direct_share_menu_id) {
onShareMenuItemSelected(id == R.id.direct_share_menu_id,
getCurrentTabModel().isIncognito());
} else if (id == R.id.print_id) {
PrintingController printingController = getChromeApplication().getPrintingController();
if (printingController != null && !printingController.isBusy()
&& PrefServiceBridge.getInstance().isPrintingEnabled()) {
printingController.startPrint(new TabPrinter(currentTab),
new PrintManagerDelegateImpl(this));
RecordUserAction.record("MobileMenuPrint");
}
} else if (id == R.id.add_to_homescreen_id) {
AddToHomescreenDialog.show(this, currentTab);
RecordUserAction.record("MobileMenuAddToHomescreen");
} else if (id == R.id.request_desktop_site_id) {
final boolean reloadOnChange = !currentTab.isNativePage();
final boolean usingDesktopUserAgent = currentTab.getUseDesktopUserAgent();
currentTab.setUseDesktopUserAgent(!usingDesktopUserAgent, reloadOnChange);
RecordUserAction.record("MobileMenuRequestDesktopSite");
} else if (id == R.id.reader_mode_prefs_id) {
if (currentTab.getWebContents() != null) {
RecordUserAction.record("DomDistiller_DistilledPagePrefsOpened");
AlertDialog.Builder builder =
new AlertDialog.Builder(this, R.style.AlertDialogTheme);
builder.setView(DistilledPagePrefsView.create(this));
builder.show();
}
} else if (id == R.id.help_id) {
// Since reading back the compositor is asynchronous, we need to do the readback
// before starting the GoogleHelp.
String helpContextId = HelpAndFeedback.getHelpContextIdFromUrl(
this, currentTab.getUrl(), getCurrentTabModel().isIncognito());
HelpAndFeedback.getInstance(this)
.show(this, helpContextId, currentTab.getProfile(), currentTab.getUrl());
RecordUserAction.record("MobileMenuFeedback");
} else {
return false;
}
return true;
}
private void markSessionResume() {
// Start new session for UMA.
if (mUmaSessionStats == null) {
mUmaSessionStats = new UmaSessionStats(this);
}
mUmaSessionStats.updateMetricsServiceState();
// In DocumentMode we need the application-level TabModelSelector instead of per
// activity which only manages a single tab.
if (FeatureUtilities.isDocumentMode(this)) {
mUmaSessionStats.startNewSession(
ChromeApplication.getDocumentTabModelSelector());
} else {
mUmaSessionStats.startNewSession(getTabModelSelector());
}
}
/**
* 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.logMultiWindowStats(windowArea(), displayArea(),
TabWindowManager.getInstance().getNumberOfAssignedTabModelSelectors());
mUmaSessionStats.logAndEndSession();
}
private int windowArea() {
Window window = getWindow();
if (window != null) {
View view = window.getDecorView();
return view.getWidth() * view.getHeight();
}
return -1;
}
private int displayArea() {
if (getResources() != null && getResources().getDisplayMetrics() != null) {
DisplayMetrics metrics = getResources().getDisplayMetrics();
return metrics.heightPixels * metrics.widthPixels;
}
return -1;
}
protected final void postDeferredStartupIfNeeded() {
if (!mDeferredStartupNotified) {
// We want to perform deferred startup tasks a short time after the first page
// load completes, but only when the main thread Looper has become idle.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!mDeferredStartupNotified && !isActivityDestroyed()) {
mDeferredStartupNotified = true;
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
onDeferredStartup();
return false; // Remove this idle handler.
}
});
}
}
}, DEFERRED_STARTUP_DELAY_MS);
}
}
/**
* Determines whether the ContentView is currently visible and not hidden by an overlay
* @return true if the ContentView is fully hidden by another view (i.e. the tab stack)
*/
public boolean isOverlayVisible() {
return false;
}
/**
* Deletes the snapshot database which is no longer used because the feature has been removed
* in Chrome M41.
*/
private void removeSnapshotDatabase() {
final Context appContext = getApplicationContext();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
synchronized (SNAPSHOT_DATABASE_LOCK) {
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(appContext);
if (!prefs.getBoolean(SNAPSHOT_DATABASE_REMOVED, false)) {
deleteDatabase(SNAPSHOT_DATABASE_NAME);
prefs.edit().putBoolean(SNAPSHOT_DATABASE_REMOVED, true).apply();
}
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void recordKeyboardLocaleUma() {
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> ims = imm.getEnabledInputMethodList();
ArrayList<String> uniqueLanguages = new ArrayList<String>();
for (InputMethodInfo method : ims) {
List<InputMethodSubtype> submethods =
imm.getEnabledInputMethodSubtypeList(method, true);
for (InputMethodSubtype submethod : submethods) {
if (submethod.getMode().equals("keyboard")) {
String language = submethod.getLocale().split("_")[0];
if (!uniqueLanguages.contains(language)) {
uniqueLanguages.add(language);
}
}
}
}
RecordHistogram.recordCountHistogram("InputMethod.ActiveCount", uniqueLanguages.size());
InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype();
Locale systemLocale = Locale.getDefault();
if (currentSubtype != null && currentSubtype.getLocale() != null && systemLocale != null) {
String keyboardLanguage = currentSubtype.getLocale().split("_")[0];
boolean match = systemLocale.getLanguage().equalsIgnoreCase(keyboardLanguage);
RecordHistogram.recordBooleanHistogram("InputMethod.MatchesSystemLanguage", match);
}
}
@Override
public void terminateIncognitoSession() {}
@Override
public void onTabSelectionHinted(int tabId) { }
@Override
public void onSceneChange(Layout layout) { }
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
// See enableHardwareAcceleration()
if (mSetWindowHWA) {
mSetWindowHWA = false;
getWindow().setWindowManager(
getWindow().getWindowManager(),
getWindow().getAttributes().token,
getComponentName().flattenToString(),
true /* hardwareAccelerated */);
}
}
/**
* @see Activity#onContextMenuClosed(Menu)
*/
@Override
public void onContextMenuClosed(Menu menu) {
final Tab currentTab = getActivityTab();
if (currentTab == null) return;
WebContents webContents = currentTab.getWebContents();
if (webContents == null) return;
webContents.onContextMenuClosed();
}
private void enableHardwareAcceleration() {
// HW acceleration is disabled in the manifest. Enable it only on high-end devices.
if (!SysUtils.isLowEndDevice()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
// When HW acceleration is enabled manually for an activity, child windows (e.g.
// dialogs) don't inherit HW acceleration state. However, when HW acceleration is
// enabled in the manifest, child windows do inherit HW acceleration state. That
// looks like a bug, so I filed b/23036374
//
// In the meanwhile the workaround is to call
// window.setWindowManager(..., hardwareAccelerated=true)
// to let the window know that it's HW accelerated. However, since there is no way
// to know 'appToken' argument until window's view is attached to the window (!!),
// we have to do the workaround in onAttachedToWindow()
mSetWindowHWA = true;
}
}
/** @return the theme ID to use. */
public static int getThemeId() {
boolean useLowEndTheme =
SysUtils.isLowEndDevice() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
return (useLowEndTheme ? R.style.MainTheme_LowEnd : R.style.MainTheme);
}
private void setLowEndTheme() {
if (getThemeId() == R.style.MainTheme_LowEnd) setTheme(R.style.MainTheme_LowEnd);
}
}