Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1071)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..00dbb8cb0c6323dfc501d24a504a635daf5cac3a
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
@@ -0,0 +1,822 @@
+// 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.document;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.AppTask;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityOptions;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Browser;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.CommandLine;
+import org.chromium.chrome.browser.BookmarkUtils;
+import org.chromium.chrome.browser.ChromeMobileApplication;
+import org.chromium.chrome.browser.ChromeSwitches;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.IntentHandler.TabOpenType;
+import org.chromium.chrome.browser.ShortcutHelper;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.UrlConstants;
+import org.chromium.chrome.browser.WarmupManager;
+import org.chromium.chrome.browser.WebappAuthenticator;
+import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
+import org.chromium.chrome.browser.hosted.HostedActivity;
+import org.chromium.chrome.browser.metrics.LaunchHistogram;
+import org.chromium.chrome.browser.metrics.LaunchMetrics;
+import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
+import org.chromium.chrome.browser.notifications.NotificationUIManager;
+import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
+import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations;
+import org.chromium.chrome.browser.preferences.DocumentModeManager;
+import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel;
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+import org.chromium.chrome.browser.util.IntentUtils;
+import org.chromium.chrome.browser.webapps.WebappActivity;
+import org.chromium.content.browser.crypto.CipherFactory;
+import org.chromium.content_public.common.ScreenOrientationValues;
+import org.chromium.ui.base.PageTransition;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * Dispatches incoming intents to the appropriate activity based on the current configuration and
+ * Intent fired.
+ */
+public class ChromeLauncherActivity extends Activity
+ implements IntentHandler.IntentHandlerDelegate {
+ /**
+ * Action fired when an Intent is trying to launch a WebappActivity.
+ * Never change the package name or the Intents will fail to launch.
+ */
+ public static final String ACTION_START_WEBAPP =
+ "com.google.android.apps.chrome.webapps.WebappManager.ACTION_START_WEBAPP";
+
+ /**
+ * Extra indicating that a Tab is trying to bring its WebappActivity to the foreground.
+ * Never change the package name or the Intents will fail to launch.
+ */
+ public static final String EXTRA_BRING_WEBAPP_TO_FRONT =
+ "com.google.android.apps.chrome.EXTRA_BRING_WEBAPP_TO_FRONT";
+
+ /**
+ * Extra indicating launch mode used.
+ */
+ public static final String EXTRA_LAUNCH_MODE =
+ "com.google.android.apps.chrome.EXTRA_LAUNCH_MODE";
+
+ /**
+ * Action fired when the user selects the "Close all incognito tabs" notification.
+ */
+ static final String ACTION_CLOSE_ALL_INCOGNITO =
+ "com.google.android.apps.chrome.document.CLOSE_ALL_INCOGNITO";
+
+ private static final String TAG = "ChromeLauncherActivity";
+
+ /** New instance should be launched in the foreground. */
+ public static final int LAUNCH_MODE_FOREGROUND = 0;
+
+ /** New instance should be launched as an affiliated task. */
+ public static final int LAUNCH_MODE_AFFILIATED = 1;
+
+ /** Existing instance should be retargetted, if possible. */
+ public static final int LAUNCH_MODE_RETARGET = 2;
+
+ private static final int FIRST_RUN_EXPERIENCE_REQUEST_CODE = 101;
+
+ /**
+ * Timeout in ms for reading PartnerBrowserCustomizations provider. We do not trust third party
+ * provider by default.
+ */
+ private static final int PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS = 10000;
+
+ /**
+ * Maximum delay for initial document activity launch.
+ */
+ private static final int INITIAL_DOCUMENT_ACTIVITY_LAUNCH_TIMEOUT_MS = 500;
+
+ private static final LaunchHistogram sMoveToFrontExceptionHistogram =
+ new LaunchHistogram("DocumentActivity.MoveToFrontFailed");
+
+ private IntentHandler mIntentHandler;
+ private boolean mIsInMultiInstanceMode;
+ private boolean mIsFinishNeeded;
+
+ /** When started with an intent, maybe pre-resolve the domain. */
+ private void maybePrefetchDnsInBackground() {
+ if (getIntent() != null && Intent.ACTION_VIEW.equals(getIntent().getAction())) {
+ String maybeUrl = IntentHandler.getUrlFromIntent(getIntent());
+ if (maybeUrl != null) {
+ WarmupManager.getInstance().maybePrefetchDnsForUrlInBackground(this, maybeUrl);
+ }
+ }
+ }
+
+ /**
+ * Figure out how to route the Intent. Because this is on the critical path to startup, please
+ * avoid making the pathway any more complicated than it already is. Make sure that anything
+ * you add _absolutely has_ to be here.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Initialize the command line in case we've disabled document mode from there.
+ ((ChromeMobileApplication) getApplication()).initCommandLine();
+
+ // Read partner browser customizations information asynchronously.
+ // We want to initialize early because when there is no tabs to restore, we should possibly
+ // show homepage, which might require reading PartnerBrowserCustomizations provider.
+ PartnerBrowserCustomizations.initializeAsync(getApplicationContext(),
+ PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS);
+
+ mIsInMultiInstanceMode = MultiWindowUtils.getInstance().shouldRunInMultiInstanceMode(this);
+ mIntentHandler = new IntentHandler(this, getPackageName());
+ maybePerformMigrationTasks();
+
+ if (handleHostedActivityIntent()) {
+ finish();
+ return;
+ }
+
+ // Check if we should launch a WebappActivity.
+ if (IntentUtils.safeGetBooleanExtra(getIntent(), EXTRA_BRING_WEBAPP_TO_FRONT, false)
+ || TextUtils.equals(getIntent().getAction(), ACTION_START_WEBAPP)) {
+ Intent fallbackIntent = launchWebapp(getIntent());
+ if (fallbackIntent == null) {
+ ApiCompatibilityUtils.finishAndRemoveTask(this);
+ return;
+ } else {
+ // Try to launch the URL as a regular VIEW intent.
+ setIntent(fallbackIntent);
+ }
+ }
+
+ // Check if we should launch the ChromeTabbedActivity.
+ if (!FeatureUtilities.isDocumentMode(this)) {
+ launchTabbedMode();
+ finish();
+ return;
+ }
+
+ // Check if we're just closing all of the Incognito tabs.
+ if (TextUtils.equals(getIntent().getAction(), ACTION_CLOSE_ALL_INCOGNITO)) {
+ ChromeMobileApplication.getDocumentTabModelSelector().getModel(true).closeAllTabs();
+ ApiCompatibilityUtils.finishAndRemoveTask(this);
+ return;
+ }
+
+ // The notification settings cog on the flipped side of Notifications and in the Android
+ // Settings "App Notifications" view will open us with a specific category.
+ if (getIntent().hasCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)) {
+ NotificationUIManager.launchNotificationPreferences(this, getIntent());
+ return;
+ }
+
+ // Check if we should launch the FirstRunActivity. This occurs after the check to launch
+ // ChromeTabbedActivity because ChromeTabbedActivity handles FRE in its own way.
+ if (launchFirstRunExperience()) return;
+
+ // Launch a DocumentActivity to handle the Intent.
+ handleDocumentActivityIntent();
+ if (!mIsFinishNeeded) ApiCompatibilityUtils.finishAndRemoveTask(this);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == FIRST_RUN_EXPERIENCE_REQUEST_CODE) {
+ if (resultCode == Activity.RESULT_OK) {
+ // User might have opted out during FRE, so check again.
+ if (FeatureUtilities.isDocumentMode(this)) {
+ handleDocumentActivityIntent();
+ if (!mIsFinishNeeded) ApiCompatibilityUtils.finishAndRemoveTask(this);
+ } else {
+ launchTabbedMode();
+ finish();
+ }
+ return;
+ }
+
+ // TODO(aruslan): FAIL.
+ ApiCompatibilityUtils.finishAndRemoveTask(this);
+ }
+ }
+
+ /**
+ * If we have just opted in or opted out of document mode, perform pending migration tasks
+ * such as cleaning up the recents.
+ */
+ private void maybePerformMigrationTasks() {
+ if (DocumentModeManager.getInstance(this).isOptOutCleanUpPending()) {
+ cleanUpChromeRecents(
+ DocumentModeManager.getInstance(this).isOptedOutOfDocumentMode());
+ DocumentModeManager.getInstance(this).setOptOutCleanUpPending(false);
+ }
+ }
+
+ @Override
+ public void processWebSearchIntent(String query) {
+ assert false;
+ }
+
+ @Override
+ public void processUrlViewIntent(String url, String headers,
+ IntentHandler.TabOpenType tabOpenType, String externalAppId,
+ int tabIdToBringToFront, Intent intent) {
+ assert false;
+ }
+
+ /**
+ * Handles launching an hosted activity, which will sit on top of a client's activity in the
+ * same task.
+ * @return True if the intent is handled here.
+ */
+ private boolean handleHostedActivityIntent() {
+ if (getIntent() == null) return false;
+
+ boolean enabled = CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_EMBEDDED_MODE);
+ boolean append = IntentUtils.safeGetBooleanExtra(
+ getIntent(), IntentHandler.EXTRA_APPEND_TASK, false);
+ if (!append || !enabled) return false;
+
+ String url = IntentHandler.getUrlFromIntent(getIntent());
+ if (url == null) return false;
+
+ // Create and fire a launch intent. Use the copy constructor to carry over the myriad of
+ // extras.
+ Intent newIntent = new Intent(getIntent());
+ newIntent.setAction(Intent.ACTION_VIEW);
+ newIntent.setClassName(this, HostedActivity.class.getName());
+ newIntent.setData(Uri.parse(url));
+ startActivity(newIntent);
+ return true;
+ }
+
+ /**
+ * Handles the launching of a DocumentActivity from the current Intent. Routing Intents to
+ * other types of Activities must be handled from onCreate() instead.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void handleDocumentActivityIntent() {
+ if (getIntent() == null || mIntentHandler.shouldIgnoreIntent(this, getIntent())) {
+ Log.e(TAG, "Ignoring intent: " + getIntent());
+ mIsFinishNeeded = true;
+ return;
+ }
+
+ maybePrefetchDnsInBackground();
+
+ // Increment the Tab ID counter at this point since this Activity may not appear in
+ // getAppTasks() when DocumentTabModelSelector is initialized. This can potentially happen
+ // when Chrome is launched via the GSA/e200 search box and they relinquish their task.
+ Tab.incrementIdCounterTo(getTaskId() + 1);
+
+ // Handle MAIN Intent actions, usually fired when the user starts Chrome via the launcher.
+ // Some launchers start Chrome by firing a VIEW Intent with an empty URL (crbug.com/459349);
+ // treat it as a MAIN Intent.
+ String url = IntentHandler.getUrlFromIntent(getIntent());
+ if ((url == null && TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW))
+ || TextUtils.equals(getIntent().getAction(), Intent.ACTION_MAIN)) {
+ handleMainDocumentIntent();
+ return;
+ }
+
+ // Sometimes an Intent requests that the current Document get clobbered.
+ if (clobberCurrentDocument(url)) return;
+
+ // Try to retarget existing Documents before creating a new one.
+ boolean incognito = IntentUtils.safeGetBooleanExtra(getIntent(),
+ IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false);
+ boolean append = IntentUtils.safeGetBooleanExtra(
+ getIntent(), IntentHandler.EXTRA_APPEND_TASK, false);
+ boolean reuse = IntentUtils.safeGetBooleanExtra(
+ getIntent(), BookmarkUtils.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, false);
+ boolean affiliated = IntentUtils.safeGetBooleanExtra(
+ getIntent(), IntentHandler.EXTRA_OPEN_IN_BG, false);
+
+ // Try to relaunch an existing task.
+ if (reuse && !append) {
+ LaunchMetrics.recordHomeScreenLaunchIntoTab(url);
+ if (relaunchTask(incognito, url)) return;
+ }
+
+ // Create and fire a launch Intent to start a new Task. The old Intent is copied using
+ // the constructor so that we pass through the myriad extras that were set on it.
+ Intent newIntent = createLaunchIntent(
+ getApplicationContext(), getIntent(), url, incognito, Tab.INVALID_TAB_ID);
+ setRecentsFlagsOnIntent(
+ newIntent, append ? 0 : Intent.FLAG_ACTIVITY_NEW_DOCUMENT, incognito);
+ fireDocumentIntent(this, newIntent, incognito, url, affiliated, null);
+ }
+
+ /**
+ * Handles actions pertaining to Chrome being started with a MAIN Intent. Typically, receiving
+ * this Intent means that a user has selected the Chrome icon from their launcher, but it is
+ * also used internally (e.g. when firing Intents back at Chrome via notifications).
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void handleMainDocumentIntent() {
+ // Bring a specific tab back to the foreground.
+ int tabId = IntentUtils.safeGetIntExtra(getIntent(),
+ TabOpenType.BRING_TAB_TO_FRONT.name(), Tab.INVALID_TAB_ID);
+ if (tabId != Tab.INVALID_TAB_ID && relaunchTask(tabId)) return;
+
+ // Bring the last viewed tab to the foreground, unless we're in Samsung's multi-instance
+ // mode -- a MAIN Intent in that case results in the creation of a second default page.
+ if (!mIsInMultiInstanceMode && launchLastViewedActivity()) return;
+
+ // Launch the default page asynchronously because the homepage URL needs to be queried.
+ // This is obviously not ideal, but we don't have a choice.
+ mIsFinishNeeded = mIsInMultiInstanceMode;
+ PartnerBrowserCustomizations.setOnInitializeAsyncFinished(new Runnable() {
+ @Override
+ public void run() {
+ String url = HomepageManager.getHomepageUri(ChromeLauncherActivity.this);
+ if (TextUtils.isEmpty(url)) url = UrlConstants.NTP_URL;
+
+ int mode = mIsInMultiInstanceMode ? LAUNCH_MODE_FOREGROUND : LAUNCH_MODE_RETARGET;
+ launchDocumentInstance(ChromeLauncherActivity.this, false, mode, url,
+ DocumentMetricIds.STARTED_BY_LAUNCHER, PageTransition.AUTO_TOPLEVEL, false,
+ null);
+
+ if (mIsFinishNeeded) finish();
+ }
+ }, INITIAL_DOCUMENT_ACTIVITY_LAUNCH_TIMEOUT_MS);
+ }
+
+ /**
+ * If necessary, attempts to clobber the current DocumentActivity's tab with the given URL.
+ * @param url URL to display.
+ * @return Whether or not the clobber was successful.
+ */
+ private boolean clobberCurrentDocument(String url) {
+ boolean shouldOpenNewTab = IntentUtils.safeGetBooleanExtra(
+ getIntent(), Browser.EXTRA_CREATE_NEW_TAB, false);
+ String applicationId =
+ IntentUtils.safeGetStringExtra(getIntent(), Browser.EXTRA_APPLICATION_ID);
+ if (shouldOpenNewTab || !getPackageName().equals(applicationId)) return false;
+
+ // Check if there's a Tab that can be clobbered.
+ int tabId = ChromeMobileApplication.getDocumentTabModelSelector().getCurrentTabId();
+ if (tabId == Tab.INVALID_TAB_ID) return false;
+
+ // Try to clobber the page.
+ PendingDocumentData data = new PendingDocumentData();
+ data.url = url;
+ data.originalIntent = new Intent(getIntent());
+ ChromeMobileApplication.getDocumentTabModelSelector().addPendingDocumentData(tabId, data);
+ if (!relaunchTask(tabId)) {
+ // Were not able to clobber, will fall through to handle in a new document.
+ ChromeMobileApplication.getDocumentTabModelSelector().removePendingDocumentData(tabId);
+ return false;
+ }
+
+ return true;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private boolean launchLastViewedActivity() {
+ int tabId = ChromeMobileApplication.getDocumentTabModelSelector().getCurrentTabId();
+ DocumentTabModel model =
+ ChromeMobileApplication.getDocumentTabModelSelector().getModelForTabId(tabId);
+ if (tabId != Tab.INVALID_TAB_ID && model != null && !model.isCoveredByChildActivity(tabId)
+ && relaunchTask(tabId)) {
+ return true;
+ }
+
+ // Everything above failed, try to launch the last viewed activity based on app tasks list.
+ ActivityManager am = (ActivityManager) getSystemService(Activity.ACTIVITY_SERVICE);
+ PackageManager pm = getPackageManager();
+ for (AppTask task : am.getAppTasks()) {
+ String className = DocumentUtils.getTaskClassName(task, pm);
+ if (className == null || !DocumentActivity.isDocumentActivity(className)) continue;
+
+ int id = ActivityDelegate.getTabIdFromIntent(task.getTaskInfo().baseIntent);
+ model = ChromeMobileApplication.getDocumentTabModelSelector().getModelForTabId(id);
+ if (model != null && model.isCoveredByChildActivity(id)) continue;
+
+ if (!moveToFront(task)) continue;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Starts a Document for the given URL.
+ *
+ * NOTE: this method adds trusted intent extra to authenticate that Chrome set the
+ * EXTRA_PAGE_TRANSITION_TYPE extra which we only want Chrome to do.
+ * This should never be exposed to non-Chrome callers.
+ * @param activity Activity launching the new instance. May be null.
+ * @param incognito Whether the created document should be incognito.
+ * @param launchMode See LAUNCH_MODE_* above.
+ * @param url URL to load.
+ * @param intentSource What is causing the Intent to be fired.
+ * See DocumentUma.DOCUMENT_ACTIVITY_STARTED_BY_
+ * @param pageTransitionType The page transition we will do on loading the given URL.
+ * @param useDesktopUserAgent Whether to use a desktop user agent.
+ * @param pendingUrlParams PendingUrlParams to store internally and use later once an intent is
+ * received to launch the URL. May be null.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static void launchDocumentInstance(Activity activity, boolean incognito, int launchMode,
+ String url, int intentSource, int pageTransitionType,
+ boolean useDesktopUserAgent, PendingDocumentData pendingUrlParams) {
+ // If we weren't given an initial URL, check the pending parameters.
+ if (url == null && pendingUrlParams != null) {
+ if (pendingUrlParams.url != null) {
+ url = pendingUrlParams.url;
+ } else if (pendingUrlParams.webContents != null) {
+ url = pendingUrlParams.webContents.getUrl();
+ }
+ }
+
+ // Try to retarget an existing task. Make sure there is no pending data to go with the load
+ // because relaunching an Activity won't send the parameters over.
+ if (launchMode == LAUNCH_MODE_RETARGET) {
+ assert pendingUrlParams == null;
+ if (relaunchTask(incognito, url)) return;
+ }
+
+ // If the new tab is spawned by another tab, record the parent.
+ int parentId = activity != null && (launchMode == LAUNCH_MODE_AFFILIATED
+ || intentSource == DocumentMetricIds.STARTED_BY_WINDOW_OPEN
+ || intentSource == DocumentMetricIds.STARTED_BY_CONTEXTUAL_SEARCH)
+ ? ActivityDelegate.getTabIdFromIntent(activity.getIntent())
+ : Tab.INVALID_TAB_ID;
+
+ // Fire an Intent to start a DocumentActivity instance.
+ Context context = ApplicationStatus.getApplicationContext();
+ Intent intent = createLaunchIntent(context, null, url, incognito, parentId);
+ setRecentsFlagsOnIntent(intent, Intent.FLAG_ACTIVITY_NEW_DOCUMENT, incognito);
+ intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, incognito);
+ intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, pageTransitionType);
+ intent.putExtra(IntentHandler.EXTRA_STARTED_BY, intentSource);
+ intent.putExtra(IntentHandler.EXTRA_USE_DESKTOP_USER_AGENT, useDesktopUserAgent);
+ intent.putExtra(EXTRA_LAUNCH_MODE, launchMode);
+ IntentHandler.addTrustedIntentExtras(intent, context);
+
+ boolean affiliated = launchMode == LAUNCH_MODE_AFFILIATED;
+ if (activity == null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ fireDocumentIntent(context, intent, incognito, url, affiliated, pendingUrlParams);
+ } else {
+ fireDocumentIntent(activity, intent, incognito, url, affiliated, pendingUrlParams);
+ }
+ }
+
+ /**
+ * Starts the document activity specified by the intent and options. Potentially first runs
+ * {@link CipherKeyActivity} in order to restore cipher keys.
+ *
+ * Note that Android has a mechanism for retargeting existing tasks via Intents, which involves
+ * firing an Intent to the same class with the same URI data. Firing an Intent via this method
+ * may therefore _not_ create a new DocumentActivity instance.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static void fireDocumentIntent(Context context, Intent intent, boolean incognito,
+ String url, boolean affiliated, PendingDocumentData pendingUrlParams) {
+ assert url != null;
+ assert incognito || TextUtils.equals(IntentHandler.getUrlFromIntent(intent), url);
+ assert !affiliated || !incognito;
+
+ // Remove any flags from the Intent that would prevent a second instance of Chrome from
+ // appearing.
+ if (context instanceof ChromeLauncherActivity
+ && ((ChromeLauncherActivity) context).mIsInMultiInstanceMode) {
+ MultiWindowUtils.getInstance().makeMultiInstanceIntent((ChromeLauncherActivity) context,
+ intent);
+ }
+
+ // Incognito URLs are not passed through the Intent for privacy reasons. Instead, store it
+ // as a parameter that gets retrieved when the IncognitoDocumentActivity starts.
+ if (incognito) {
+ if (pendingUrlParams == null) pendingUrlParams = new PendingDocumentData();
+ assert pendingUrlParams.url == null;
+ pendingUrlParams.url = url;
+ }
+
+ // Store parameters for the new DocumentActivity, which are retrieved immediately after the
+ // new Activity starts. This structure is used to avoid passing things like pointers to
+ // native WebContents in the Intent, which are strictly under Android's control and is
+ // re-delivered when a Chrome Activity is restarted.
+ boolean isWebContentsPending = false;
+ if (pendingUrlParams != null) {
+ int tabId = ActivityDelegate.getTabIdFromIntent(intent);
+ ChromeMobileApplication.getDocumentTabModelSelector().addPendingDocumentData(
+ tabId, pendingUrlParams);
+ isWebContentsPending = pendingUrlParams.webContents != null;
+ }
+
+ Bundle options = affiliated && !isWebContentsPending
+ ? ActivityOptions.makeTaskLaunchBehind().toBundle() : null;
+ if (incognito && !CipherFactory.getInstance().hasCipher()
+ && ChromeMobileApplication.getDocumentTabModelSelector().getModel(true)
+ .getCount() > 0) {
+ // The CipherKeyActivity needs to be run to restore the Incognito decryption key.
+ Intent cipherIntent = CipherKeyActivity.createIntent(context, intent, options);
+ context.startActivity(cipherIntent);
+ } else {
+ context.startActivity(intent, options);
+ }
+ }
+
+ /**
+ * Get an intent that will close all incognito tabs through {@link ChromeLauncherActivity}.
+ * @param context The context to use for creating the {@link PendingIntent}.
+ * @return {@link PendingIntent} to use for closing all incognito tabs.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static PendingIntent getRemoveAllIncognitoTabsIntent(Context context) {
+ Intent intent = new Intent(
+ ACTION_CLOSE_ALL_INCOGNITO, null, context, ChromeLauncherActivity.class);
+ return PendingIntent.getActivity(context, 0, intent, 0);
+ }
+
+ static String getDocumentClassName(boolean isIncognito) {
+ return isIncognito ? IncognitoDocumentActivity.class.getName() :
+ DocumentActivity.class.getName();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static Intent createLaunchIntent(
+ Context context, Intent oldIntent, String url, boolean incognito, int parentId) {
+ int newTabId = ChromeMobileApplication.getDocumentTabModelSelector().generateValidTabId();
+
+ // Copy the old Intent so that the extras carry over.
+ Intent intent = oldIntent == null ? new Intent() : new Intent(oldIntent);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setClassName(context, getDocumentClassName(incognito));
+
+ if (incognito) {
+ // Incognito Intents don't pass URLs in their data.
+ intent.setData(DocumentTabModelSelector.createDocumentDataString(newTabId, ""));
+ } else {
+ intent.setData(DocumentTabModelSelector.createDocumentDataString(newTabId, url));
+ }
+
+ // For content URIs, because intent.getData().getScheme() begins with "document://,
+ // we need to pass a ClipData so DocumentActivity can access the content.
+ if (url != null && url.startsWith("content://")) {
+ intent.setClipData(ClipData.newUri(
+ context.getContentResolver(), "content", Uri.parse(url)));
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ intent.putExtra(IntentHandler.EXTRA_PARENT_TAB_ID, parentId);
+ if (oldIntent != null && Intent.ACTION_VIEW.equals(oldIntent.getAction())) {
+ intent.putExtra(IntentHandler.EXTRA_ORIGINAL_INTENT, oldIntent);
+ }
+
+ return intent;
+ }
+
+ @SuppressLint("InlinedApi")
+ private void launchTabbedMode() {
+ maybePrefetchDnsInBackground();
+
+ Intent newIntent = new Intent(getIntent());
+ newIntent.setClassName(getApplicationContext().getPackageName(),
+ ChromeTabbedActivity.class.getName());
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+ }
+ Uri uri = newIntent.getData();
+ if (uri != null && "content".equals(uri.getScheme())) {
+ newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ if (mIsInMultiInstanceMode) {
+ MultiWindowUtils.getInstance().makeMultiInstanceIntent(this, newIntent);
+ }
+ startActivity(newIntent);
+ }
+
+ /**
+ * Bring the task matching the given tab ID to the front.
+ * @param tabId tab ID to search for.
+ * @return Whether the task was successfully brought back.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean relaunchTask(int tabId) {
+ if (tabId == Tab.INVALID_TAB_ID) return false;
+
+ Context context = ApplicationStatus.getApplicationContext();
+ ActivityManager manager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (AppTask task : manager.getAppTasks()) {
+ RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task);
+ if (info == null) continue;
+
+ int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent);
+ if (id != tabId) continue;
+
+ DocumentTabModelSelector.setPrioritizedTabId(id);
+ if (!moveToFront(task)) continue;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Bring the task matching the given URL to the front if the task is retargetable.
+ * @param incognito Whether or not the tab is incognito.
+ * @param url URL that the tab would have been created for. If null, this param is ignored.
+ * @return Whether the task was successfully brought back.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean relaunchTask(boolean incognito, String url) {
+ if (TextUtils.isEmpty(url)) return false;
+
+ Context context = ApplicationStatus.getApplicationContext();
+ ActivityManager manager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (AppTask task : manager.getAppTasks()) {
+ RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task);
+ if (info == null) continue;
+
+ String initialUrl = ActivityDelegate.getInitialUrlForDocument(info.baseIntent);
+ if (TextUtils.isEmpty(initialUrl) || !TextUtils.equals(initialUrl, url)) continue;
+
+ int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent);
+ DocumentTabModelSelector.setPrioritizedTabId(id);
+ if (!ChromeMobileApplication.getDocumentTabModelSelector().getModel(incognito)
+ .isRetargetable(id)) {
+ continue;
+ }
+
+ if (!moveToFront(task)) continue;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * On opting out, remove all the old tasks from the recents.
+ * @param fromDocument Whether any possible migration was from document mode to classic.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void cleanUpChromeRecents(boolean fromDocument) {
+ ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ List<ActivityManager.AppTask> taskList = am.getAppTasks();
+ PackageManager pm = getPackageManager();
+ for (int i = 0; i < taskList.size(); i++) {
+ AppTask task = taskList.get(i);
+ String className = DocumentUtils.getTaskClassName(task, pm);
+ if (className == null) continue;
+
+ RecentTaskInfo taskInfo = DocumentUtils.getTaskInfoFromTask(task);
+ if (taskInfo == null) continue;
+
+ // Skip the document activities if we are migrating from classic to document.
+ boolean skip = !fromDocument && DocumentActivity.isDocumentActivity(className);
+ if (!skip && (taskInfo.id != getTaskId())) {
+ taskList.get(i).finishAndRemoveTask();
+ }
+ }
+ }
+
+ /**
+ * Set flags that ensure that we control when our Activities disappear from Recents.
+ * @param intent Intent to set the flags on.
+ * @param extraFlags Other flags to add to the Intent, 0 if there's nothing to add.
+ * @param incognito Whether we are launching an incognito document.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static void setRecentsFlagsOnIntent(Intent intent, int extraFlags, boolean incognito) {
+ intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ if (!incognito) intent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+ if (extraFlags != 0) intent.addFlags(extraFlags);
+ }
+
+ /**
+ * @return Whether there is already an browser instance of Chrome already running.
+ */
+ public boolean isChromeBrowserActivityRunning() {
+ for (WeakReference<Activity> reference : ApplicationStatus.getRunningActivities()) {
+ Activity activity = reference.get();
+ if (activity == null) continue;
+
+ String className = activity.getClass().getName();
+ if (DocumentActivity.isDocumentActivity(className)
+ || TextUtils.equals(className, ChromeTabbedActivity.class.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to move a task back to the front. This can FAIL for some reason because the UID
+ * of the DocumentActivity we try to bring back to the front doesn't match the
+ * ChromeLauncherActivities.
+ * @param task Task to attempt to bring back to the foreground.
+ * @return Whether or not this succeeded.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean moveToFront(AppTask task) {
+ try {
+ task.moveToFront();
+ return true;
+ } catch (SecurityException e) {
+ sMoveToFrontExceptionHistogram.recordHit();
+ }
+ return false;
+ }
+
+ /**
+ * Tries to launch a WebappActivity for the given Intent.
+ * @return Intent to fire if the webapp Intent failed to launch because of security checks,
+ * null otherwise.
+ */
+ private Intent launchWebapp(Intent intent) {
+ String webappId = IntentUtils.safeGetStringExtra(intent, ShortcutHelper.EXTRA_ID);
+ String webappUrl = IntentUtils.safeGetStringExtra(intent, ShortcutHelper.EXTRA_URL);
+ String webappTitle = IntentUtils.safeGetStringExtra(intent, ShortcutHelper.EXTRA_TITLE);
+ String webappIcon = IntentUtils.safeGetStringExtra(intent, ShortcutHelper.EXTRA_ICON);
+ int webappOrientation = IntentUtils.safeGetIntExtra(intent,
+ ShortcutHelper.EXTRA_ORIENTATION, ScreenOrientationValues.DEFAULT);
+
+ if (webappId != null && webappUrl != null) {
+ String webappMacString = IntentUtils.safeGetStringExtra(
+ intent, ShortcutHelper.EXTRA_MAC);
+ byte[] webappMac =
+ webappMacString == null ? null : Base64.decode(webappMacString, Base64.DEFAULT);
+
+ if (webappMac != null && WebappAuthenticator.isUrlValid(this, webappUrl, webappMac)) {
+ if (TextUtils.equals(ACTION_START_WEBAPP, intent.getAction())) {
+ LaunchMetrics.recordHomeScreenLaunchIntoStandaloneActivity(webappUrl);
+ }
+
+ WebappActivity.launchInstance(
+ this, webappId, webappUrl, webappIcon, webappTitle, webappOrientation);
+ } else {
+ Log.e(TAG, "Shortcut (" + webappUrl + ") opened in Chrome.");
+
+ // Tried and failed. Change the intent action and try the URL with a VIEW Intent.
+ Intent fallbackIntent = new Intent(intent);
+ fallbackIntent.setAction(Intent.ACTION_VIEW);
+ fallbackIntent.setData(Uri.parse(webappUrl));
+ fallbackIntent.putExtra(BookmarkUtils.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, true);
+ return fallbackIntent;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Tries to launch the First Run Experience. If ChromeLauncherActivity is running with the
+ * wrong Intent flags, we instead relaunch ChromeLauncherActivity to make sure it runs in its
+ * own task, which then triggers First Run.
+ * @return Whether or not the First Run Experience needed to be shown.
+ */
+ private boolean launchFirstRunExperience() {
+ final boolean isIntentActionMain = getIntent() != null
+ && TextUtils.equals(getIntent().getAction(), Intent.ACTION_MAIN);
+ final Intent freIntent = FirstRunFlowSequencer.checkIfFirstRunIsNecessary(
+ this, getIntent(), isIntentActionMain);
+ if (freIntent == null) return false;
+
+ if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ startActivityForResult(freIntent, FIRST_RUN_EXPERIENCE_REQUEST_CODE);
+ } else {
+ Intent newIntent = new Intent(getIntent());
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(newIntent);
+ finish();
+ }
+ return true;
+ }
+
+ /**
+ * Send the number of times an exception was caught when trying to move a task back to front.
+ */
+ public static void sendExceptionCount() {
+ sMoveToFrontExceptionHistogram.commitHistogram();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698