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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.document;
6
7 import android.annotation.SuppressLint;
8 import android.annotation.TargetApi;
9 import android.app.Activity;
10 import android.app.ActivityManager;
11 import android.app.ActivityManager.AppTask;
12 import android.app.ActivityManager.RecentTaskInfo;
13 import android.app.ActivityOptions;
14 import android.app.Notification;
15 import android.app.PendingIntent;
16 import android.content.ClipData;
17 import android.content.Context;
18 import android.content.Intent;
19 import android.content.pm.PackageManager;
20 import android.net.Uri;
21 import android.os.Build;
22 import android.os.Bundle;
23 import android.provider.Browser;
24 import android.text.TextUtils;
25 import android.util.Base64;
26 import android.util.Log;
27
28 import org.chromium.base.ApiCompatibilityUtils;
29 import org.chromium.base.ApplicationStatus;
30 import org.chromium.base.CommandLine;
31 import org.chromium.chrome.browser.BookmarkUtils;
32 import org.chromium.chrome.browser.ChromeMobileApplication;
33 import org.chromium.chrome.browser.ChromeSwitches;
34 import org.chromium.chrome.browser.ChromeTabbedActivity;
35 import org.chromium.chrome.browser.IntentHandler;
36 import org.chromium.chrome.browser.IntentHandler.TabOpenType;
37 import org.chromium.chrome.browser.ShortcutHelper;
38 import org.chromium.chrome.browser.Tab;
39 import org.chromium.chrome.browser.UrlConstants;
40 import org.chromium.chrome.browser.WarmupManager;
41 import org.chromium.chrome.browser.WebappAuthenticator;
42 import org.chromium.chrome.browser.firstrun.FirstRunFlowSequencer;
43 import org.chromium.chrome.browser.hosted.HostedActivity;
44 import org.chromium.chrome.browser.metrics.LaunchHistogram;
45 import org.chromium.chrome.browser.metrics.LaunchMetrics;
46 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
47 import org.chromium.chrome.browser.notifications.NotificationUIManager;
48 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
49 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomiza tions;
50 import org.chromium.chrome.browser.preferences.DocumentModeManager;
51 import org.chromium.chrome.browser.tabmodel.document.ActivityDelegate;
52 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel;
53 import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector;
54 import org.chromium.chrome.browser.util.FeatureUtilities;
55 import org.chromium.chrome.browser.util.IntentUtils;
56 import org.chromium.chrome.browser.webapps.WebappActivity;
57 import org.chromium.content.browser.crypto.CipherFactory;
58 import org.chromium.content_public.common.ScreenOrientationValues;
59 import org.chromium.ui.base.PageTransition;
60
61 import java.lang.ref.WeakReference;
62 import java.util.List;
63
64 /**
65 * Dispatches incoming intents to the appropriate activity based on the current configuration and
66 * Intent fired.
67 */
68 public class ChromeLauncherActivity extends Activity
69 implements IntentHandler.IntentHandlerDelegate {
70 /**
71 * Action fired when an Intent is trying to launch a WebappActivity.
72 * Never change the package name or the Intents will fail to launch.
73 */
74 public static final String ACTION_START_WEBAPP =
75 "com.google.android.apps.chrome.webapps.WebappManager.ACTION_START_W EBAPP";
76
77 /**
78 * Extra indicating that a Tab is trying to bring its WebappActivity to the foreground.
79 * Never change the package name or the Intents will fail to launch.
80 */
81 public static final String EXTRA_BRING_WEBAPP_TO_FRONT =
82 "com.google.android.apps.chrome.EXTRA_BRING_WEBAPP_TO_FRONT";
83
84 /**
85 * Extra indicating launch mode used.
86 */
87 public static final String EXTRA_LAUNCH_MODE =
88 "com.google.android.apps.chrome.EXTRA_LAUNCH_MODE";
89
90 /**
91 * Action fired when the user selects the "Close all incognito tabs" notific ation.
92 */
93 static final String ACTION_CLOSE_ALL_INCOGNITO =
94 "com.google.android.apps.chrome.document.CLOSE_ALL_INCOGNITO";
95
96 private static final String TAG = "ChromeLauncherActivity";
97
98 /** New instance should be launched in the foreground. */
99 public static final int LAUNCH_MODE_FOREGROUND = 0;
100
101 /** New instance should be launched as an affiliated task. */
102 public static final int LAUNCH_MODE_AFFILIATED = 1;
103
104 /** Existing instance should be retargetted, if possible. */
105 public static final int LAUNCH_MODE_RETARGET = 2;
106
107 private static final int FIRST_RUN_EXPERIENCE_REQUEST_CODE = 101;
108
109 /**
110 * Timeout in ms for reading PartnerBrowserCustomizations provider. We do no t trust third party
111 * provider by default.
112 */
113 private static final int PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS = 10000;
114
115 /**
116 * Maximum delay for initial document activity launch.
117 */
118 private static final int INITIAL_DOCUMENT_ACTIVITY_LAUNCH_TIMEOUT_MS = 500;
119
120 private static final LaunchHistogram sMoveToFrontExceptionHistogram =
121 new LaunchHistogram("DocumentActivity.MoveToFrontFailed");
122
123 private IntentHandler mIntentHandler;
124 private boolean mIsInMultiInstanceMode;
125 private boolean mIsFinishNeeded;
126
127 /** When started with an intent, maybe pre-resolve the domain. */
128 private void maybePrefetchDnsInBackground() {
129 if (getIntent() != null && Intent.ACTION_VIEW.equals(getIntent().getActi on())) {
130 String maybeUrl = IntentHandler.getUrlFromIntent(getIntent());
131 if (maybeUrl != null) {
132 WarmupManager.getInstance().maybePrefetchDnsForUrlInBackground(t his, maybeUrl);
133 }
134 }
135 }
136
137 /**
138 * Figure out how to route the Intent. Because this is on the critical path to startup, please
139 * avoid making the pathway any more complicated than it already is. Make s ure that anything
140 * you add _absolutely has_ to be here.
141 */
142 @Override
143 public void onCreate(Bundle savedInstanceState) {
144 super.onCreate(savedInstanceState);
145
146 // Initialize the command line in case we've disabled document mode from there.
147 ((ChromeMobileApplication) getApplication()).initCommandLine();
148
149 // Read partner browser customizations information asynchronously.
150 // We want to initialize early because when there is no tabs to restore, we should possibly
151 // show homepage, which might require reading PartnerBrowserCustomizatio ns provider.
152 PartnerBrowserCustomizations.initializeAsync(getApplicationContext(),
153 PARTNER_BROWSER_CUSTOMIZATIONS_TIMEOUT_MS);
154
155 mIsInMultiInstanceMode = MultiWindowUtils.getInstance().shouldRunInMulti InstanceMode(this);
156 mIntentHandler = new IntentHandler(this, getPackageName());
157 maybePerformMigrationTasks();
158
159 if (handleHostedActivityIntent()) {
160 finish();
161 return;
162 }
163
164 // Check if we should launch a WebappActivity.
165 if (IntentUtils.safeGetBooleanExtra(getIntent(), EXTRA_BRING_WEBAPP_TO_F RONT, false)
166 || TextUtils.equals(getIntent().getAction(), ACTION_START_WEBAPP )) {
167 Intent fallbackIntent = launchWebapp(getIntent());
168 if (fallbackIntent == null) {
169 ApiCompatibilityUtils.finishAndRemoveTask(this);
170 return;
171 } else {
172 // Try to launch the URL as a regular VIEW intent.
173 setIntent(fallbackIntent);
174 }
175 }
176
177 // Check if we should launch the ChromeTabbedActivity.
178 if (!FeatureUtilities.isDocumentMode(this)) {
179 launchTabbedMode();
180 finish();
181 return;
182 }
183
184 // Check if we're just closing all of the Incognito tabs.
185 if (TextUtils.equals(getIntent().getAction(), ACTION_CLOSE_ALL_INCOGNITO )) {
186 ChromeMobileApplication.getDocumentTabModelSelector().getModel(true) .closeAllTabs();
187 ApiCompatibilityUtils.finishAndRemoveTask(this);
188 return;
189 }
190
191 // The notification settings cog on the flipped side of Notifications an d in the Android
192 // Settings "App Notifications" view will open us with a specific catego ry.
193 if (getIntent().hasCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PR EFERENCES)) {
194 NotificationUIManager.launchNotificationPreferences(this, getIntent( ));
195 return;
196 }
197
198 // Check if we should launch the FirstRunActivity. This occurs after th e check to launch
199 // ChromeTabbedActivity because ChromeTabbedActivity handles FRE in its own way.
200 if (launchFirstRunExperience()) return;
201
202 // Launch a DocumentActivity to handle the Intent.
203 handleDocumentActivityIntent();
204 if (!mIsFinishNeeded) ApiCompatibilityUtils.finishAndRemoveTask(this);
205 }
206
207 @Override
208 protected void onActivityResult(int requestCode, int resultCode, Intent data ) {
209 super.onActivityResult(requestCode, resultCode, data);
210 if (requestCode == FIRST_RUN_EXPERIENCE_REQUEST_CODE) {
211 if (resultCode == Activity.RESULT_OK) {
212 // User might have opted out during FRE, so check again.
213 if (FeatureUtilities.isDocumentMode(this)) {
214 handleDocumentActivityIntent();
215 if (!mIsFinishNeeded) ApiCompatibilityUtils.finishAndRemoveT ask(this);
216 } else {
217 launchTabbedMode();
218 finish();
219 }
220 return;
221 }
222
223 // TODO(aruslan): FAIL.
224 ApiCompatibilityUtils.finishAndRemoveTask(this);
225 }
226 }
227
228 /**
229 * If we have just opted in or opted out of document mode, perform pending m igration tasks
230 * such as cleaning up the recents.
231 */
232 private void maybePerformMigrationTasks() {
233 if (DocumentModeManager.getInstance(this).isOptOutCleanUpPending()) {
234 cleanUpChromeRecents(
235 DocumentModeManager.getInstance(this).isOptedOutOfDocumentMo de());
236 DocumentModeManager.getInstance(this).setOptOutCleanUpPending(false) ;
237 }
238 }
239
240 @Override
241 public void processWebSearchIntent(String query) {
242 assert false;
243 }
244
245 @Override
246 public void processUrlViewIntent(String url, String headers,
247 IntentHandler.TabOpenType tabOpenType, String externalAppId,
248 int tabIdToBringToFront, Intent intent) {
249 assert false;
250 }
251
252 /**
253 * Handles launching an hosted activity, which will sit on top of a client's activity in the
254 * same task.
255 * @return True if the intent is handled here.
256 */
257 private boolean handleHostedActivityIntent() {
258 if (getIntent() == null) return false;
259
260 boolean enabled = CommandLine.getInstance().hasSwitch(ChromeSwitches.ENA BLE_EMBEDDED_MODE);
261 boolean append = IntentUtils.safeGetBooleanExtra(
262 getIntent(), IntentHandler.EXTRA_APPEND_TASK, false);
263 if (!append || !enabled) return false;
264
265 String url = IntentHandler.getUrlFromIntent(getIntent());
266 if (url == null) return false;
267
268 // Create and fire a launch intent. Use the copy constructor to carry ov er the myriad of
269 // extras.
270 Intent newIntent = new Intent(getIntent());
271 newIntent.setAction(Intent.ACTION_VIEW);
272 newIntent.setClassName(this, HostedActivity.class.getName());
273 newIntent.setData(Uri.parse(url));
274 startActivity(newIntent);
275 return true;
276 }
277
278 /**
279 * Handles the launching of a DocumentActivity from the current Intent. Rou ting Intents to
280 * other types of Activities must be handled from onCreate() instead.
281 */
282 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
283 private void handleDocumentActivityIntent() {
284 if (getIntent() == null || mIntentHandler.shouldIgnoreIntent(this, getIn tent())) {
285 Log.e(TAG, "Ignoring intent: " + getIntent());
286 mIsFinishNeeded = true;
287 return;
288 }
289
290 maybePrefetchDnsInBackground();
291
292 // Increment the Tab ID counter at this point since this Activity may no t appear in
293 // getAppTasks() when DocumentTabModelSelector is initialized. This can potentially happen
294 // when Chrome is launched via the GSA/e200 search box and they relinqui sh their task.
295 Tab.incrementIdCounterTo(getTaskId() + 1);
296
297 // Handle MAIN Intent actions, usually fired when the user starts Chrome via the launcher.
298 // Some launchers start Chrome by firing a VIEW Intent with an empty URL (crbug.com/459349);
299 // treat it as a MAIN Intent.
300 String url = IntentHandler.getUrlFromIntent(getIntent());
301 if ((url == null && TextUtils.equals(getIntent().getAction(), Intent.ACT ION_VIEW))
302 || TextUtils.equals(getIntent().getAction(), Intent.ACTION_MAIN) ) {
303 handleMainDocumentIntent();
304 return;
305 }
306
307 // Sometimes an Intent requests that the current Document get clobbered.
308 if (clobberCurrentDocument(url)) return;
309
310 // Try to retarget existing Documents before creating a new one.
311 boolean incognito = IntentUtils.safeGetBooleanExtra(getIntent(),
312 IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false);
313 boolean append = IntentUtils.safeGetBooleanExtra(
314 getIntent(), IntentHandler.EXTRA_APPEND_TASK, false);
315 boolean reuse = IntentUtils.safeGetBooleanExtra(
316 getIntent(), BookmarkUtils.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, false);
317 boolean affiliated = IntentUtils.safeGetBooleanExtra(
318 getIntent(), IntentHandler.EXTRA_OPEN_IN_BG, false);
319
320 // Try to relaunch an existing task.
321 if (reuse && !append) {
322 LaunchMetrics.recordHomeScreenLaunchIntoTab(url);
323 if (relaunchTask(incognito, url)) return;
324 }
325
326 // Create and fire a launch Intent to start a new Task. The old Intent is copied using
327 // the constructor so that we pass through the myriad extras that were s et on it.
328 Intent newIntent = createLaunchIntent(
329 getApplicationContext(), getIntent(), url, incognito, Tab.INVALI D_TAB_ID);
330 setRecentsFlagsOnIntent(
331 newIntent, append ? 0 : Intent.FLAG_ACTIVITY_NEW_DOCUMENT, incog nito);
332 fireDocumentIntent(this, newIntent, incognito, url, affiliated, null);
333 }
334
335 /**
336 * Handles actions pertaining to Chrome being started with a MAIN Intent. T ypically, receiving
337 * this Intent means that a user has selected the Chrome icon from their lau ncher, but it is
338 * also used internally (e.g. when firing Intents back at Chrome via notific ations).
339 */
340 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
341 private void handleMainDocumentIntent() {
342 // Bring a specific tab back to the foreground.
343 int tabId = IntentUtils.safeGetIntExtra(getIntent(),
344 TabOpenType.BRING_TAB_TO_FRONT.name(), Tab.INVALID_TAB_ID);
345 if (tabId != Tab.INVALID_TAB_ID && relaunchTask(tabId)) return;
346
347 // Bring the last viewed tab to the foreground, unless we're in Samsung' s multi-instance
348 // mode -- a MAIN Intent in that case results in the creation of a secon d default page.
349 if (!mIsInMultiInstanceMode && launchLastViewedActivity()) return;
350
351 // Launch the default page asynchronously because the homepage URL needs to be queried.
352 // This is obviously not ideal, but we don't have a choice.
353 mIsFinishNeeded = mIsInMultiInstanceMode;
354 PartnerBrowserCustomizations.setOnInitializeAsyncFinished(new Runnable() {
355 @Override
356 public void run() {
357 String url = HomepageManager.getHomepageUri(ChromeLauncherActivi ty.this);
358 if (TextUtils.isEmpty(url)) url = UrlConstants.NTP_URL;
359
360 int mode = mIsInMultiInstanceMode ? LAUNCH_MODE_FOREGROUND : LAU NCH_MODE_RETARGET;
361 launchDocumentInstance(ChromeLauncherActivity.this, false, mode, url,
362 DocumentMetricIds.STARTED_BY_LAUNCHER, PageTransition.AU TO_TOPLEVEL, false,
363 null);
364
365 if (mIsFinishNeeded) finish();
366 }
367 }, INITIAL_DOCUMENT_ACTIVITY_LAUNCH_TIMEOUT_MS);
368 }
369
370 /**
371 * If necessary, attempts to clobber the current DocumentActivity's tab with the given URL.
372 * @param url URL to display.
373 * @return Whether or not the clobber was successful.
374 */
375 private boolean clobberCurrentDocument(String url) {
376 boolean shouldOpenNewTab = IntentUtils.safeGetBooleanExtra(
377 getIntent(), Browser.EXTRA_CREATE_NEW_TAB, false);
378 String applicationId =
379 IntentUtils.safeGetStringExtra(getIntent(), Browser.EXTRA_APPLIC ATION_ID);
380 if (shouldOpenNewTab || !getPackageName().equals(applicationId)) return false;
381
382 // Check if there's a Tab that can be clobbered.
383 int tabId = ChromeMobileApplication.getDocumentTabModelSelector().getCur rentTabId();
384 if (tabId == Tab.INVALID_TAB_ID) return false;
385
386 // Try to clobber the page.
387 PendingDocumentData data = new PendingDocumentData();
388 data.url = url;
389 data.originalIntent = new Intent(getIntent());
390 ChromeMobileApplication.getDocumentTabModelSelector().addPendingDocument Data(tabId, data);
391 if (!relaunchTask(tabId)) {
392 // Were not able to clobber, will fall through to handle in a new do cument.
393 ChromeMobileApplication.getDocumentTabModelSelector().removePendingD ocumentData(tabId);
394 return false;
395 }
396
397 return true;
398 }
399
400 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
401 private boolean launchLastViewedActivity() {
402 int tabId = ChromeMobileApplication.getDocumentTabModelSelector().getCur rentTabId();
403 DocumentTabModel model =
404 ChromeMobileApplication.getDocumentTabModelSelector().getModelFo rTabId(tabId);
405 if (tabId != Tab.INVALID_TAB_ID && model != null && !model.isCoveredByCh ildActivity(tabId)
406 && relaunchTask(tabId)) {
407 return true;
408 }
409
410 // Everything above failed, try to launch the last viewed activity based on app tasks list.
411 ActivityManager am = (ActivityManager) getSystemService(Activity.ACTIVIT Y_SERVICE);
412 PackageManager pm = getPackageManager();
413 for (AppTask task : am.getAppTasks()) {
414 String className = DocumentUtils.getTaskClassName(task, pm);
415 if (className == null || !DocumentActivity.isDocumentActivity(classN ame)) continue;
416
417 int id = ActivityDelegate.getTabIdFromIntent(task.getTaskInfo().base Intent);
418 model = ChromeMobileApplication.getDocumentTabModelSelector().getMod elForTabId(id);
419 if (model != null && model.isCoveredByChildActivity(id)) continue;
420
421 if (!moveToFront(task)) continue;
422 return true;
423 }
424 return false;
425 }
426
427 /**
428 * Starts a Document for the given URL.
429 *
430 * NOTE: this method adds trusted intent extra to authenticate that Chrome s et the
431 * EXTRA_PAGE_TRANSITION_TYPE extra which we only want Chrome to do.
432 * This should never be exposed to non-Chrome callers.
433 * @param activity Activity launching the new instance. May be null.
434 * @param incognito Whether the created document should be incognito.
435 * @param launchMode See LAUNCH_MODE_* above.
436 * @param url URL to load.
437 * @param intentSource What is causing the Intent to be fired.
438 * See DocumentUma.DOCUMENT_ACTIVITY_STARTED_BY_
439 * @param pageTransitionType The page transition we will do on loading the g iven URL.
440 * @param useDesktopUserAgent Whether to use a desktop user agent.
441 * @param pendingUrlParams PendingUrlParams to store internally and use late r once an intent is
442 * received to launch the URL. May be null.
443 */
444 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
445 public static void launchDocumentInstance(Activity activity, boolean incogni to, int launchMode,
446 String url, int intentSource, int pageTransitionType,
447 boolean useDesktopUserAgent, PendingDocumentData pendingUrlParams) {
448 // If we weren't given an initial URL, check the pending parameters.
449 if (url == null && pendingUrlParams != null) {
450 if (pendingUrlParams.url != null) {
451 url = pendingUrlParams.url;
452 } else if (pendingUrlParams.webContents != null) {
453 url = pendingUrlParams.webContents.getUrl();
454 }
455 }
456
457 // Try to retarget an existing task. Make sure there is no pending data to go with the load
458 // because relaunching an Activity won't send the parameters over.
459 if (launchMode == LAUNCH_MODE_RETARGET) {
460 assert pendingUrlParams == null;
461 if (relaunchTask(incognito, url)) return;
462 }
463
464 // If the new tab is spawned by another tab, record the parent.
465 int parentId = activity != null && (launchMode == LAUNCH_MODE_AFFILIATED
466 || intentSource == DocumentMetricIds.STARTED_BY_WINDOW_OPEN
467 || intentSource == DocumentMetricIds.STARTED_BY_CONTEXTUAL_SEARC H)
468 ? ActivityDelegate.getTabIdFromIntent(activity.getIntent())
469 : Tab.INVALID_TAB_ID;
470
471 // Fire an Intent to start a DocumentActivity instance.
472 Context context = ApplicationStatus.getApplicationContext();
473 Intent intent = createLaunchIntent(context, null, url, incognito, parent Id);
474 setRecentsFlagsOnIntent(intent, Intent.FLAG_ACTIVITY_NEW_DOCUMENT, incog nito);
475 intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, incognito);
476 intent.putExtra(IntentHandler.EXTRA_PAGE_TRANSITION_TYPE, pageTransition Type);
477 intent.putExtra(IntentHandler.EXTRA_STARTED_BY, intentSource);
478 intent.putExtra(IntentHandler.EXTRA_USE_DESKTOP_USER_AGENT, useDesktopUs erAgent);
479 intent.putExtra(EXTRA_LAUNCH_MODE, launchMode);
480 IntentHandler.addTrustedIntentExtras(intent, context);
481
482 boolean affiliated = launchMode == LAUNCH_MODE_AFFILIATED;
483 if (activity == null) {
484 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
485 fireDocumentIntent(context, intent, incognito, url, affiliated, pend ingUrlParams);
486 } else {
487 fireDocumentIntent(activity, intent, incognito, url, affiliated, pen dingUrlParams);
488 }
489 }
490
491 /**
492 * Starts the document activity specified by the intent and options. Potenti ally first runs
493 * {@link CipherKeyActivity} in order to restore cipher keys.
494 *
495 * Note that Android has a mechanism for retargeting existing tasks via Inte nts, which involves
496 * firing an Intent to the same class with the same URI data. Firing an Int ent via this method
497 * may therefore _not_ create a new DocumentActivity instance.
498 */
499 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
500 private static void fireDocumentIntent(Context context, Intent intent, boole an incognito,
501 String url, boolean affiliated, PendingDocumentData pendingUrlParams ) {
502 assert url != null;
503 assert incognito || TextUtils.equals(IntentHandler.getUrlFromIntent(inte nt), url);
504 assert !affiliated || !incognito;
505
506 // Remove any flags from the Intent that would prevent a second instance of Chrome from
507 // appearing.
508 if (context instanceof ChromeLauncherActivity
509 && ((ChromeLauncherActivity) context).mIsInMultiInstanceMode) {
510 MultiWindowUtils.getInstance().makeMultiInstanceIntent((ChromeLaunch erActivity) context,
511 intent);
512 }
513
514 // Incognito URLs are not passed through the Intent for privacy reasons. Instead, store it
515 // as a parameter that gets retrieved when the IncognitoDocumentActivity starts.
516 if (incognito) {
517 if (pendingUrlParams == null) pendingUrlParams = new PendingDocument Data();
518 assert pendingUrlParams.url == null;
519 pendingUrlParams.url = url;
520 }
521
522 // Store parameters for the new DocumentActivity, which are retrieved im mediately after the
523 // new Activity starts. This structure is used to avoid passing things like pointers to
524 // native WebContents in the Intent, which are strictly under Android's control and is
525 // re-delivered when a Chrome Activity is restarted.
526 boolean isWebContentsPending = false;
527 if (pendingUrlParams != null) {
528 int tabId = ActivityDelegate.getTabIdFromIntent(intent);
529 ChromeMobileApplication.getDocumentTabModelSelector().addPendingDocu mentData(
530 tabId, pendingUrlParams);
531 isWebContentsPending = pendingUrlParams.webContents != null;
532 }
533
534 Bundle options = affiliated && !isWebContentsPending
535 ? ActivityOptions.makeTaskLaunchBehind().toBundle() : null;
536 if (incognito && !CipherFactory.getInstance().hasCipher()
537 && ChromeMobileApplication.getDocumentTabModelSelector().getMode l(true)
538 .getCount() > 0) {
539 // The CipherKeyActivity needs to be run to restore the Incognito de cryption key.
540 Intent cipherIntent = CipherKeyActivity.createIntent(context, intent , options);
541 context.startActivity(cipherIntent);
542 } else {
543 context.startActivity(intent, options);
544 }
545 }
546
547 /**
548 * Get an intent that will close all incognito tabs through {@link ChromeLau ncherActivity}.
549 * @param context The context to use for creating the {@link PendingIntent}.
550 * @return {@link PendingIntent} to use for closing all incognito tabs.
551 */
552 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
553 public static PendingIntent getRemoveAllIncognitoTabsIntent(Context context) {
554 Intent intent = new Intent(
555 ACTION_CLOSE_ALL_INCOGNITO, null, context, ChromeLauncherActivit y.class);
556 return PendingIntent.getActivity(context, 0, intent, 0);
557 }
558
559 static String getDocumentClassName(boolean isIncognito) {
560 return isIncognito ? IncognitoDocumentActivity.class.getName() :
561 DocumentActivity.class.getName();
562 }
563
564 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
565 private static Intent createLaunchIntent(
566 Context context, Intent oldIntent, String url, boolean incognito, in t parentId) {
567 int newTabId = ChromeMobileApplication.getDocumentTabModelSelector().gen erateValidTabId();
568
569 // Copy the old Intent so that the extras carry over.
570 Intent intent = oldIntent == null ? new Intent() : new Intent(oldIntent) ;
571 intent.setAction(Intent.ACTION_VIEW);
572 intent.setClassName(context, getDocumentClassName(incognito));
573
574 if (incognito) {
575 // Incognito Intents don't pass URLs in their data.
576 intent.setData(DocumentTabModelSelector.createDocumentDataString(new TabId, ""));
577 } else {
578 intent.setData(DocumentTabModelSelector.createDocumentDataString(new TabId, url));
579 }
580
581 // For content URIs, because intent.getData().getScheme() begins with "d ocument://,
582 // we need to pass a ClipData so DocumentActivity can access the content .
583 if (url != null && url.startsWith("content://")) {
584 intent.setClipData(ClipData.newUri(
585 context.getContentResolver(), "content", Uri.parse(url)));
586 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
587 }
588 intent.putExtra(IntentHandler.EXTRA_PARENT_TAB_ID, parentId);
589 if (oldIntent != null && Intent.ACTION_VIEW.equals(oldIntent.getAction() )) {
590 intent.putExtra(IntentHandler.EXTRA_ORIGINAL_INTENT, oldIntent);
591 }
592
593 return intent;
594 }
595
596 @SuppressLint("InlinedApi")
597 private void launchTabbedMode() {
598 maybePrefetchDnsInBackground();
599
600 Intent newIntent = new Intent(getIntent());
601 newIntent.setClassName(getApplicationContext().getPackageName(),
602 ChromeTabbedActivity.class.getName());
603 newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY _NEW_TASK);
604 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
605 newIntent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
606 }
607 Uri uri = newIntent.getData();
608 if (uri != null && "content".equals(uri.getScheme())) {
609 newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
610 }
611 if (mIsInMultiInstanceMode) {
612 MultiWindowUtils.getInstance().makeMultiInstanceIntent(this, newInte nt);
613 }
614 startActivity(newIntent);
615 }
616
617 /**
618 * Bring the task matching the given tab ID to the front.
619 * @param tabId tab ID to search for.
620 * @return Whether the task was successfully brought back.
621 */
622 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
623 private static boolean relaunchTask(int tabId) {
624 if (tabId == Tab.INVALID_TAB_ID) return false;
625
626 Context context = ApplicationStatus.getApplicationContext();
627 ActivityManager manager =
628 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERV ICE);
629 for (AppTask task : manager.getAppTasks()) {
630 RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task);
631 if (info == null) continue;
632
633 int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent);
634 if (id != tabId) continue;
635
636 DocumentTabModelSelector.setPrioritizedTabId(id);
637 if (!moveToFront(task)) continue;
638
639 return true;
640 }
641
642 return false;
643 }
644
645 /**
646 * Bring the task matching the given URL to the front if the task is retarge table.
647 * @param incognito Whether or not the tab is incognito.
648 * @param url URL that the tab would have been created for. If null, this pa ram is ignored.
649 * @return Whether the task was successfully brought back.
650 */
651 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
652 private static boolean relaunchTask(boolean incognito, String url) {
653 if (TextUtils.isEmpty(url)) return false;
654
655 Context context = ApplicationStatus.getApplicationContext();
656 ActivityManager manager =
657 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERV ICE);
658 for (AppTask task : manager.getAppTasks()) {
659 RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task);
660 if (info == null) continue;
661
662 String initialUrl = ActivityDelegate.getInitialUrlForDocument(info.b aseIntent);
663 if (TextUtils.isEmpty(initialUrl) || !TextUtils.equals(initialUrl, u rl)) continue;
664
665 int id = ActivityDelegate.getTabIdFromIntent(info.baseIntent);
666 DocumentTabModelSelector.setPrioritizedTabId(id);
667 if (!ChromeMobileApplication.getDocumentTabModelSelector().getModel( incognito)
668 .isRetargetable(id)) {
669 continue;
670 }
671
672 if (!moveToFront(task)) continue;
673 return true;
674 }
675
676 return false;
677 }
678
679 /**
680 * On opting out, remove all the old tasks from the recents.
681 * @param fromDocument Whether any possible migration was from document mode to classic.
682 */
683 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
684 private void cleanUpChromeRecents(boolean fromDocument) {
685 ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY _SERVICE);
686 List<ActivityManager.AppTask> taskList = am.getAppTasks();
687 PackageManager pm = getPackageManager();
688 for (int i = 0; i < taskList.size(); i++) {
689 AppTask task = taskList.get(i);
690 String className = DocumentUtils.getTaskClassName(task, pm);
691 if (className == null) continue;
692
693 RecentTaskInfo taskInfo = DocumentUtils.getTaskInfoFromTask(task);
694 if (taskInfo == null) continue;
695
696 // Skip the document activities if we are migrating from classic to document.
697 boolean skip = !fromDocument && DocumentActivity.isDocumentActivity( className);
698 if (!skip && (taskInfo.id != getTaskId())) {
699 taskList.get(i).finishAndRemoveTask();
700 }
701 }
702 }
703
704 /**
705 * Set flags that ensure that we control when our Activities disappear from Recents.
706 * @param intent Intent to set the flags on.
707 * @param extraFlags Other flags to add to the Intent, 0 if there's nothing to add.
708 * @param incognito Whether we are launching an incognito document.
709 */
710 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
711 private static void setRecentsFlagsOnIntent(Intent intent, int extraFlags, b oolean incognito) {
712 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_R ECENTS);
713 if (!incognito) intent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
714 if (extraFlags != 0) intent.addFlags(extraFlags);
715 }
716
717 /**
718 * @return Whether there is already an browser instance of Chrome already ru nning.
719 */
720 public boolean isChromeBrowserActivityRunning() {
721 for (WeakReference<Activity> reference : ApplicationStatus.getRunningAct ivities()) {
722 Activity activity = reference.get();
723 if (activity == null) continue;
724
725 String className = activity.getClass().getName();
726 if (DocumentActivity.isDocumentActivity(className)
727 || TextUtils.equals(className, ChromeTabbedActivity.class.ge tName())) {
728 return true;
729 }
730 }
731 return false;
732 }
733
734 /**
735 * Attempt to move a task back to the front. This can FAIL for some reason because the UID
736 * of the DocumentActivity we try to bring back to the front doesn't match t he
737 * ChromeLauncherActivities.
738 * @param task Task to attempt to bring back to the foreground.
739 * @return Whether or not this succeeded.
740 */
741 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
742 private static boolean moveToFront(AppTask task) {
743 try {
744 task.moveToFront();
745 return true;
746 } catch (SecurityException e) {
747 sMoveToFrontExceptionHistogram.recordHit();
748 }
749 return false;
750 }
751
752 /**
753 * Tries to launch a WebappActivity for the given Intent.
754 * @return Intent to fire if the webapp Intent failed to launch because of s ecurity checks,
755 * null otherwise.
756 */
757 private Intent launchWebapp(Intent intent) {
758 String webappId = IntentUtils.safeGetStringExtra(intent, ShortcutHelper. EXTRA_ID);
759 String webappUrl = IntentUtils.safeGetStringExtra(intent, ShortcutHelper .EXTRA_URL);
760 String webappTitle = IntentUtils.safeGetStringExtra(intent, ShortcutHelp er.EXTRA_TITLE);
761 String webappIcon = IntentUtils.safeGetStringExtra(intent, ShortcutHelpe r.EXTRA_ICON);
762 int webappOrientation = IntentUtils.safeGetIntExtra(intent,
763 ShortcutHelper.EXTRA_ORIENTATION, ScreenOrientationValues.DEFAUL T);
764
765 if (webappId != null && webappUrl != null) {
766 String webappMacString = IntentUtils.safeGetStringExtra(
767 intent, ShortcutHelper.EXTRA_MAC);
768 byte[] webappMac =
769 webappMacString == null ? null : Base64.decode(webappMacStri ng, Base64.DEFAULT);
770
771 if (webappMac != null && WebappAuthenticator.isUrlValid(this, webapp Url, webappMac)) {
772 if (TextUtils.equals(ACTION_START_WEBAPP, intent.getAction())) {
773 LaunchMetrics.recordHomeScreenLaunchIntoStandaloneActivity(w ebappUrl);
774 }
775
776 WebappActivity.launchInstance(
777 this, webappId, webappUrl, webappIcon, webappTitle, weba ppOrientation);
778 } else {
779 Log.e(TAG, "Shortcut (" + webappUrl + ") opened in Chrome.");
780
781 // Tried and failed. Change the intent action and try the URL w ith a VIEW Intent.
782 Intent fallbackIntent = new Intent(intent);
783 fallbackIntent.setAction(Intent.ACTION_VIEW);
784 fallbackIntent.setData(Uri.parse(webappUrl));
785 fallbackIntent.putExtra(BookmarkUtils.REUSE_URL_MATCHING_TAB_ELS E_NEW_TAB, true);
786 return fallbackIntent;
787 }
788 }
789 return null;
790 }
791
792 /**
793 * Tries to launch the First Run Experience. If ChromeLauncherActivity is r unning with the
794 * wrong Intent flags, we instead relaunch ChromeLauncherActivity to make su re it runs in its
795 * own task, which then triggers First Run.
796 * @return Whether or not the First Run Experience needed to be shown.
797 */
798 private boolean launchFirstRunExperience() {
799 final boolean isIntentActionMain = getIntent() != null
800 && TextUtils.equals(getIntent().getAction(), Intent.ACTION_MAIN) ;
801 final Intent freIntent = FirstRunFlowSequencer.checkIfFirstRunIsNecessar y(
802 this, getIntent(), isIntentActionMain);
803 if (freIntent == null) return false;
804
805 if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
806 startActivityForResult(freIntent, FIRST_RUN_EXPERIENCE_REQUEST_CODE) ;
807 } else {
808 Intent newIntent = new Intent(getIntent());
809 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
810 startActivity(newIntent);
811 finish();
812 }
813 return true;
814 }
815
816 /**
817 * Send the number of times an exception was caught when trying to move a ta sk back to front.
818 */
819 public static void sendExceptionCount() {
820 sMoveToFrontExceptionHistogram.commitHistogram();
821 }
822 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698