diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 16:35:47 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-11-18 15:45:54 +0000 |
commit | 32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch) | |
tree | eeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/components/external_intents | |
parent | 99677208ff3b216fdfec551fbe548da5520cd6fb (diff) | |
download | qtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz |
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/external_intents')
10 files changed, 0 insertions, 3141 deletions
diff --git a/chromium/components/external_intents/android/BUILD.gn b/chromium/components/external_intents/android/BUILD.gn index 1d5981d364c..2eccc0ef9a0 100644 --- a/chromium/components/external_intents/android/BUILD.gn +++ b/chromium/components/external_intents/android/BUILD.gn @@ -38,7 +38,6 @@ android_library("java") { android_resources("java_resources") { sources = [] - create_srcjar = false deps = [ "//components/browser_ui/strings/android:browser_ui_strings_grd", "//components/browser_ui/styles/android:java_resources", diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/AuthenticatorNavigationInterceptor.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/AuthenticatorNavigationInterceptor.java deleted file mode 100644 index d1a8937fe54..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/AuthenticatorNavigationInterceptor.java +++ /dev/null @@ -1,20 +0,0 @@ -// 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.components.external_intents; - -/** - * Handles intercepting navigation requests for an external authenticator application. - */ -public interface AuthenticatorNavigationInterceptor { - /** - * To be called by a Tab to check whether the passed in URL, which is about to be loaded, - * should be processed by an external Authenticator application. - * - * @param url the URL about to be loaded in the tab - * @return True if the URL has been handled by the Authenticator, false if it hasn't and - * should be processed normally by the Tab. - */ - boolean handleAuthenticatorUrl(String url); -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatureList.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatureList.java deleted file mode 100644 index f4fbcba6383..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsFeatureList.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 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.components.external_intents; - -import org.chromium.base.FeatureList; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.MainDex; -import org.chromium.base.annotations.NativeMethods; - -/** - * Java accessor for base/feature_list.h state. - * - * This class provides methods to access values of feature flags registered in - * |kFeaturesExposedToJava| in components/external_intents/android/external_intents_feature_list.cc. - * - */ -@JNINamespace("external_intents") -@MainDex -public abstract class ExternalIntentsFeatureList { - /** Prevent instantiation. */ - private ExternalIntentsFeatureList() {} - - /** - * Returns whether the specified feature is enabled or not. - * - * Note: Features queried through this API must be added to the array - * |kFeaturesExposedToJava| in - * components/external_intents/android/external_intents_feature_list.cc. - * - * Calling this has the side effect of bucketing this client, which may cause an experiment to - * be marked as active. - * - * Should be called only after native is loaded. - * - * @param featureName The name of the feature to query. - * @return Whether the feature is enabled or not. - */ - public static boolean isEnabled(String featureName) { - assert FeatureList.isNativeInitialized(); - return ExternalIntentsFeatureListJni.get().isEnabled(featureName); - } - - /** Alphabetical: */ - public static final String INTENT_BLOCK_EXTERNAL_FORM_REDIRECT_NO_GESTURE = - "IntentBlockExternalFormRedirectsNoGesture"; - - @NativeMethods - interface Natives { - boolean isEnabled(String featureName); - } -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsSwitches.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsSwitches.java deleted file mode 100644 index 67860830ce3..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalIntentsSwitches.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020 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.components.external_intents; - -/** - * Contains all of the command line switches for external intent launching. - */ -public abstract class ExternalIntentsSwitches { - /** Never forward URL requests to external intents. */ - public static final String DISABLE_EXTERNAL_INTENT_REQUESTS = - "disable-external-intent-requests"; - - // Prevent instantiation. - private ExternalIntentsSwitches() {} -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java deleted file mode 100644 index 77a97b2383a..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationDelegate.java +++ /dev/null @@ -1,229 +0,0 @@ -// 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.components.external_intents; - -import android.content.Context; -import android.content.Intent; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; -import org.chromium.content_public.browser.LoadUrlParams; -import org.chromium.content_public.browser.WebContents; -import org.chromium.ui.base.WindowAndroid; -import org.chromium.url.Origin; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * A delegate for {@link ExternalNavigationHandler}. - */ -public interface ExternalNavigationDelegate { - /** - * Returns the Context with which this delegate is associated, or null if there is no such - * Context at the time of invocation. The returned Context may or may not be a wrapper around an - * Activity with which the delegate is associated. Note that when obtaining resources, however, - * the handler should do so directly via the returned Context (i.e., not via the Activity that - * it is wrapping, even if it is in fact wrapping one). The reason is that some embedders handle - * resource fetching via special logic in the ContextWrapper object that is wrapping the - * Activity. - */ - Context getContext(); - - /** - * Determine if this app is the default or only handler for a given intent. If true, this app - * will handle the intent when started. - */ - boolean willAppHandleIntent(Intent intent); - - /** - * Returns whether to disable forwarding URL requests to external intents for the passed-in URL. - */ - boolean shouldDisableExternalIntentRequestsForUrl(String url); - - /** - * Returns whether the embedder has custom integration with InstantApps (most embedders will not - * have any such integration). - */ - boolean handlesInstantAppLaunchingInternally(); - - /** - * Dispatches the intent through a proxy activity, so that startActivityForResult can be used - * and the intent recipient can verify the caller. Will be invoked only in flows where - * ExternalNavigationDelegate#isIntentForInstantApp() returns true for |intent|. In particular, - * if that method always returns false in the given embedder, then the embedder's implementation - * of this method will never be invoked and can just assert false. - * @param intent The bare intent we were going to send. - */ - void dispatchAuthenticatedIntent(Intent intent); - - /** - * Informs the delegate that an Activity was started for an external intent (some embedders wish - * to log this information, primarily for testing purposes). - */ - void didStartActivity(Intent intent); - - /** - * Used by maybeHandleStartActivityIfNeeded() below. - */ - @IntDef({StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START, - StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START, - StartActivityIfNeededResult.DID_NOT_HANDLE}) - @Retention(RetentionPolicy.SOURCE) - public @interface StartActivityIfNeededResult { - int HANDLED_WITH_ACTIVITY_START = 0; - int HANDLED_WITHOUT_ACTIVITY_START = 1; - int DID_NOT_HANDLE = 2; - } - - /** - * Gives the embedder the opportunity to handle starting an activity for the intent. Used for - * intents that may be handled internally or externally. If the embedder handles this intent, - * this method should return StartActivityIfNeededResult.HANDLED_{WITH, WITHOUT}_ACTIVITY_START - * as appropriate. To have ExternalNavigationHandler handle this intent, return - * StartActivityIfNeededResult.NOT_HANDLED. - * @param intent The intent we want to send. - * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is - * used by Instant Apps intents). - */ - @StartActivityIfNeededResult - int maybeHandleStartActivityIfNeeded(Intent intent, boolean proxy); - - /** - * Handle the incognito intent by loading it as a URL in the embedder, using the fallbackUrl if - * the intent URL cannot be handled by the embedder. - * @param intent The intent to be handled by the embedder. - * @param referrerUrl The referrer for the current navigation. - * @param fallbackUrl The fallback URL to load if the intent cannot be handled by the embedder. - * @return The OverrideUrlLoadingResult for the action taken by the embedder. - */ - @OverrideUrlLoadingResult - int handleIncognitoIntentTargetingSelf(Intent intent, String referrerUrl, String fallbackUrl); - - /** - * Loads a URL as specified by |loadUrlParams| if possible. May fail in exceptional conditions - * (e.g., if there is no valid tab). - * @param loadUrlParams parameters of the URL to be loaded - */ - void loadUrlIfPossible(LoadUrlParams loadUrlParams); - - /** Adds a window id to the intent, if necessary. */ - void maybeSetWindowId(Intent intent); - - /** Records the pending referrer if desired. */ - void maybeSetPendingReferrer(Intent intent, @NonNull String referrerUrl); - - /** - * Adjusts any desired extras related to intents to instant apps based on the value of - * |insIntentToInstantApp}. - */ - void maybeAdjustInstantAppExtras(Intent intent, boolean isIntentToInstantApp); - - /** - * Invoked for intents with request metadata such as user gesture, whether request is renderer - * initiated and the initiator origin. Records the information if desired. - */ - void maybeSetRequestMetadata(Intent intent, boolean hasUserGesture, boolean isRendererInitiated, - @Nullable Origin initiatorOrigin); - - /** - * Records the pending incognito URL if desired. Called only if the - * navigation is occurring in the context of incognito mode. - */ - void maybeSetPendingIncognitoUrl(Intent intent); - - /** - * Determine if the application of the embedder is in the foreground. - */ - boolean isApplicationInForeground(); - - /** - * Check if the URL should be handled by an instant app, or kick off an async request for an - * instant app banner. - * @param url The current URL. - * @param referrerUrl The referrer URL. - * @param isIncomingRedirect Whether we are handling an incoming redirect to an instant app. - * @param isSerpReferrer whether the referrer is the SERP. - * @return Whether we launched an instant app. - */ - boolean maybeLaunchInstantApp( - String url, String referrerUrl, boolean isIncomingRedirect, boolean isSerpReferrer); - - /** - * @return The WindowAndroid instance associated with this delegate instance. - */ - WindowAndroid getWindowAndroid(); - - /** - * @return The WebContents instance associated with this delegate instance. - */ - WebContents getWebContents(); - - /** - * @return Whether this delegate has a valid tab available. - */ - boolean hasValidTab(); - - /** - * @return Whether it's possible to close the current tab on launching on an incognito intent. - * TODO(blundell): Investigate whether it would be feasible to change the //chrome - * implementation of this method to be identical to that of its implementation of - * ExternalNavigationDelegate#hasValidTab() and then eliminate this method in favor of - * ExternalNavigationHandler calling hasValidTab() if so. - */ - boolean canCloseTabOnIncognitoIntentLaunch(); - - /** - * @return whether this delegate supports creation of new tabs. If this method returns false, - * all URLs loaded by ExternalNavigationHandler will be loaded in the current tab and - * loadUrlInNewTab() will never be invoked. - */ - boolean supportsCreatingNewTabs(); - - /** - * Loads |url| in a new tab. - * @param url The URL to load. - * @param launchIncognito whether the new tab should be incognito. - */ - void loadUrlInNewTab(final String url, final boolean launchIncognito); - - /** - * @return whether it's possible to load a URL in the current tab. - */ - boolean canLoadUrlInCurrentTab(); - - /* Invoked when the tab associated with this delegate should be closed. */ - void closeTab(); - - /* Returns whether whether the tab associated with this delegate is incognito. */ - boolean isIncognito(); - - /** - * @param intent The intent to launch. - * @return Whether the Intent points to an app that we trust and that launched this app. - */ - boolean isIntentForTrustedCallingApp(Intent intent); - - /** - * @param intent The intent to launch. - * @return Whether the Intent points to an instant app. - */ - boolean isIntentToInstantApp(Intent intent); - - /** - * @param intent The intent to launch - * @return Whether the Intent points to Autofill Assistant - */ - boolean isIntentToAutofillAssistant(Intent intent); - - /** - * Gives the embedder a chance to handle the intent via the autofill assistant. - */ - boolean handleWithAutofillAssistant(ExternalNavigationParams params, Intent targetIntent, - String browserFallbackUrl, boolean isGoogleReferrer); -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java deleted file mode 100644 index 612bade393f..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java +++ /dev/null @@ -1,1837 +0,0 @@ -// 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.components.external_intents; - -import android.Manifest.permission; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.StrictMode; -import android.os.SystemClock; -import android.provider.Browser; -import android.provider.Telephony; -import android.text.TextUtils; -import android.util.Pair; -import android.view.WindowManager.BadTokenException; -import android.webkit.MimeTypeMap; -import android.webkit.WebView; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.CommandLine; -import org.chromium.base.ContextUtils; -import org.chromium.base.IntentUtils; -import org.chromium.base.Log; -import org.chromium.base.PackageManagerUtils; -import org.chromium.base.PathUtils; -import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.base.task.PostTask; -import org.chromium.components.embedder_support.util.UrlConstants; -import org.chromium.components.embedder_support.util.UrlUtilities; -import org.chromium.components.embedder_support.util.UrlUtilitiesJni; -import org.chromium.components.webapk.lib.client.ChromeWebApkHostSignature; -import org.chromium.components.webapk.lib.client.WebApkValidator; -import org.chromium.content_public.browser.LoadUrlParams; -import org.chromium.content_public.browser.NavigationController; -import org.chromium.content_public.browser.NavigationEntry; -import org.chromium.content_public.browser.UiThreadTaskTraits; -import org.chromium.content_public.common.ContentUrlConstants; -import org.chromium.content_public.common.Referrer; -import org.chromium.network.mojom.ReferrerPolicy; -import org.chromium.ui.UiUtils; -import org.chromium.ui.base.PageTransition; -import org.chromium.ui.base.PermissionCallback; -import org.chromium.url.URI; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - -/** - * Logic related to the URL overriding/intercepting functionality. - * This feature supports conversion of certain navigations to Android Intents allowing - * applications like Youtube to direct users clicking on a http(s) link to their native app. - */ -public class ExternalNavigationHandler { - private static final String TAG = "UrlHandler"; - - // Enables debug logging on a local build. - private static final boolean DEBUG = false; - - private static final String WTAI_URL_PREFIX = "wtai://wp/"; - private static final String WTAI_MC_URL_PREFIX = "wtai://wp/mc;"; - - private static final String PLAY_PACKAGE_PARAM = "id"; - private static final String PLAY_REFERRER_PARAM = "referrer"; - private static final String PLAY_APP_PATH = "/store/apps/details"; - private static final String PLAY_HOSTNAME = "play.google.com"; - - private static final String PDF_EXTENSION = "pdf"; - private static final String PDF_VIEWER = "com.google.android.apps.docs"; - private static final String PDF_MIME = "application/pdf"; - private static final String PDF_SUFFIX = ".pdf"; - - /** - * Records package names of external applications in the system that could have handled this - * intent. - */ - public static final String EXTRA_EXTERNAL_NAV_PACKAGES = "org.chromium.chrome.browser.eenp"; - - @VisibleForTesting - public static final String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url"; - - // An extra that may be specified on an intent:// URL that contains an encoded value for the - // referrer field passed to the market:// URL in the case where the app is not present. - @VisibleForTesting - static final String EXTRA_MARKET_REFERRER = "market_referrer"; - - // A mask of flags that are safe for untrusted content to use when starting an Activity. - // This list is not exhaustive and flags not listed here are not necessarily unsafe. - @VisibleForTesting - static final int ALLOWED_INTENT_FLAGS = Intent.FLAG_EXCLUDE_STOPPED_PACKAGES - | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP - | Intent.FLAG_ACTIVITY_MATCH_EXTERNAL | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT - | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; - - // These values are persisted in histograms. Please do not renumber. Append only. - @IntDef({AiaIntent.FALLBACK_USED, AiaIntent.SERP, AiaIntent.OTHER}) - @Retention(RetentionPolicy.SOURCE) - private @interface AiaIntent { - int FALLBACK_USED = 0; - int SERP = 1; - int OTHER = 2; - - int NUM_ENTRIES = 3; - } - - // Standard Activity Actions, as defined by: - // https://developer.android.com/reference/android/content/Intent.html#standard-activity-actions - // These values are persisted in histograms. Please do not renumber. - @IntDef({StandardActions.MAIN, StandardActions.VIEW, StandardActions.ATTACH_DATA, - StandardActions.EDIT, StandardActions.PICK, StandardActions.CHOOSER, - StandardActions.GET_CONTENT, StandardActions.DIAL, StandardActions.CALL, - StandardActions.SEND, StandardActions.SENDTO, StandardActions.ANSWER, - StandardActions.INSERT, StandardActions.DELETE, StandardActions.RUN, - StandardActions.SYNC, StandardActions.PICK_ACTIVITY, StandardActions.SEARCH, - StandardActions.WEB_SEARCH, StandardActions.FACTORY_TEST, StandardActions.OTHER}) - @Retention(RetentionPolicy.SOURCE) - @VisibleForTesting - @interface StandardActions { - int MAIN = 0; - int VIEW = 1; - int ATTACH_DATA = 2; - int EDIT = 3; - int PICK = 4; - int CHOOSER = 5; - int GET_CONTENT = 6; - int DIAL = 7; - int CALL = 8; - int SEND = 9; - int SENDTO = 10; - int ANSWER = 11; - int INSERT = 12; - int DELETE = 13; - int RUN = 14; - int SYNC = 15; - int PICK_ACTIVITY = 16; - int SEARCH = 17; - int WEB_SEARCH = 18; - int FACTORY_TEST = 19; - int OTHER = 20; - - int NUM_ENTRIES = 21; - } - - @VisibleForTesting - static final String INTENT_ACTION_HISTOGRAM = "Android.Intent.OverrideUrlLoadingIntentAction"; - - // Helper class to return a boolean by reference. - private static class MutableBoolean { - private Boolean mValue; - public void set(boolean value) { - mValue = value; - } - public Boolean get() { - return mValue; - } - } - - private final ExternalNavigationDelegate mDelegate; - - /** - * Result types for checking if we should override URL loading. - * NOTE: this enum is used in UMA, do not reorder values. Changes should be append only. - * Values should be numerated from 0 and can't have gaps. - */ - @IntDef({OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, - OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, - OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION, - OverrideUrlLoadingResult.NO_OVERRIDE}) - @Retention(RetentionPolicy.SOURCE) - public @interface OverrideUrlLoadingResult { - /* We should override the URL loading and launch an intent. */ - int OVERRIDE_WITH_EXTERNAL_INTENT = 0; - /* We should override the URL loading and clobber the current tab. */ - int OVERRIDE_WITH_CLOBBERING_TAB = 1; - /* We should override the URL loading. The desired action will be determined - * asynchronously (e.g. by requiring user confirmation). */ - int OVERRIDE_WITH_ASYNC_ACTION = 2; - /* We shouldn't override the URL loading. */ - int NO_OVERRIDE = 3; - - int NUM_ENTRIES = 4; - } - - /** - * Constructs a new instance of {@link ExternalNavigationHandler}, using the injected - * {@link ExternalNavigationDelegate}. - */ - public ExternalNavigationHandler(ExternalNavigationDelegate delegate) { - mDelegate = delegate; - } - - /** - * Determines whether the URL needs to be sent as an intent to the system, - * and sends it, if appropriate. - * @return Whether the URL generated an intent, caused a navigation in - * current tab, or wasn't handled at all. - */ - public @OverrideUrlLoadingResult int shouldOverrideUrlLoading(ExternalNavigationParams params) { - if (DEBUG) Log.i(TAG, "shouldOverrideUrlLoading called on " + params.getUrl()); - Intent targetIntent; - // Perform generic parsing of the URI to turn it into an Intent. - try { - targetIntent = Intent.parseUri(params.getUrl(), Intent.URI_INTENT_SCHEME); - } catch (Exception ex) { - Log.w(TAG, "Bad URI %s", params.getUrl(), ex); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - String browserFallbackUrl = - IntentUtils.safeGetStringExtra(targetIntent, EXTRA_BROWSER_FALLBACK_URL); - if (browserFallbackUrl != null - && !UrlUtilities.isValidForIntentFallbackNavigation(browserFallbackUrl)) { - browserFallbackUrl = null; - } - - // TODO(https://crbug.com/1096099): Refactor shouldOverrideUrlLoadingInternal, splitting it - // up to separate out the notions wanting to fire an external intent vs being able to. - MutableBoolean canLaunchExternalFallbackResult = new MutableBoolean(); - - long time = SystemClock.elapsedRealtime(); - @OverrideUrlLoadingResult - int result = shouldOverrideUrlLoadingInternal( - params, targetIntent, browserFallbackUrl, canLaunchExternalFallbackResult); - assert canLaunchExternalFallbackResult.get() != null; - RecordHistogram.recordTimesHistogram( - "Android.StrictMode.OverrideUrlLoadingTime", SystemClock.elapsedRealtime() - time); - - if (result != OverrideUrlLoadingResult.NO_OVERRIDE) { - int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK; - boolean isFormSubmit = pageTransitionCore == PageTransition.FORM_SUBMIT; - boolean isRedirectFromFormSubmit = isFormSubmit && params.isRedirect(); - if (isRedirectFromFormSubmit) { - RecordHistogram.recordBooleanHistogram( - "Android.Intent.LaunchExternalAppFormSubmitHasUserGesture", - params.hasUserGesture()); - } - } else if (result == OverrideUrlLoadingResult.NO_OVERRIDE && browserFallbackUrl != null - && (params.getRedirectHandler() == null - // For instance, if this is a chained fallback URL, we ignore it. - || !params.getRedirectHandler().shouldNotOverrideUrlLoading())) { - result = handleFallbackUrl(params, targetIntent, browserFallbackUrl, - canLaunchExternalFallbackResult.get()); - } - if (DEBUG) printDebugShouldOverrideUrlLoadingResult(result); - return result; - } - - private @OverrideUrlLoadingResult int handleFallbackUrl(ExternalNavigationParams params, - Intent targetIntent, String browserFallbackUrl, boolean canLaunchExternalFallback) { - if (mDelegate.isIntentToInstantApp(targetIntent)) { - RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent", - AiaIntent.FALLBACK_USED, AiaIntent.NUM_ENTRIES); - } - - if (canLaunchExternalFallback) { - if (shouldBlockAllExternalAppLaunches(params) || params.isIncognito()) { - throw new SecurityException("Context is not allowed to launch an external app."); - } - // Launch WebAPK if it can handle the URL. - try { - Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME); - sanitizeQueryIntentActivitiesIntent(intent); - List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); - if (!isAlreadyInTargetWebApk(resolvingInfos, params) - && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - } catch (Exception e) { - if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent"); - } - - // If the fallback URL is a link to Play Store, send the user to Play Store app - // instead: crbug.com/638672. - Pair<String, String> appInfo = maybeGetPlayStoreAppIdAndReferrer(browserFallbackUrl); - if (appInfo != null) { - String marketReferrer = TextUtils.isEmpty(appInfo.second) - ? ContextUtils.getApplicationContext().getPackageName() - : appInfo.second; - return sendIntentToMarket(appInfo.first, marketReferrer, params); - } - } - - // For subframes, we don't support fallback url for now. - // http://crbug.com/364522. - if (!params.isMainFrame()) { - if (DEBUG) Log.i(TAG, "Don't support fallback url in subframes"); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - // NOTE: any further redirection from fall-back URL should not override URL loading. - // Otherwise, it can be used in chain for fingerprinting multiple app installation - // status in one shot. In order to prevent this scenario, we notify redirection - // handler that redirection from the current navigation should stay in this app. - if (params.getRedirectHandler() != null) { - params.getRedirectHandler().setShouldNotOverrideUrlLoadingOnCurrentRedirectChain(); - } - if (DEBUG) Log.i(TAG, "clobberCurrentTab called"); - return clobberCurrentTab(browserFallbackUrl, params.getReferrerUrl()); - } - - private void printDebugShouldOverrideUrlLoadingResult(int result) { - String resultString; - switch (result) { - case OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT: - resultString = "OVERRIDE_WITH_EXTERNAL_INTENT"; - break; - case OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB: - resultString = "OVERRIDE_WITH_CLOBBERING_TAB"; - break; - case OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION: - resultString = "OVERRIDE_WITH_ASYNC_ACTION"; - break; - case OverrideUrlLoadingResult.NO_OVERRIDE: // Fall through. - default: - resultString = "NO_OVERRIDE"; - break; - } - Log.i(TAG, "shouldOverrideUrlLoading result: " + resultString); - } - - private boolean resolversSubsetOf(List<ResolveInfo> infos, List<ResolveInfo> container) { - if (container == null) return false; - HashSet<ComponentName> containerSet = new HashSet<>(); - for (ResolveInfo info : container) { - containerSet.add( - new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); - } - for (ResolveInfo info : infos) { - if (!containerSet.contains( - new ComponentName(info.activityInfo.packageName, info.activityInfo.name))) { - return false; - } - } - return true; - } - - /** - * https://crbug.com/1094442: Don't allow any external navigation on AUTO_SUBFRAME navigation - * (eg. initial ad frame navigation). - */ - private boolean blockExternalNavFromAutoSubframe(ExternalNavigationParams params) { - int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK; - if (pageTransitionCore == PageTransition.AUTO_SUBFRAME) { - if (DEBUG) Log.i(TAG, "Auto navigation in subframe"); - return true; - } - return false; - } - - /** - * http://crbug.com/441284 : Disallow firing external intent while the app is in the background. - */ - private boolean blockExternalNavWhileBackgrounded(ExternalNavigationParams params) { - if (params.isApplicationMustBeInForeground() && !mDelegate.isApplicationInForeground()) { - if (DEBUG) Log.i(TAG, "App is not in foreground"); - return true; - } - return false; - } - - /** http://crbug.com/464669 : Disallow firing external intent from background tab. */ - private boolean blockExternalNavFromBackgroundTab(ExternalNavigationParams params) { - if (params.isBackgroundTabNavigation()) { - if (DEBUG) Log.i(TAG, "Navigation in background tab"); - return true; - } - return false; - } - - /** - * http://crbug.com/164194 . A navigation forwards or backwards should never trigger the intent - * picker. - */ - private boolean ignoreBackForwardNav(ExternalNavigationParams params) { - if ((params.getPageTransition() & PageTransition.FORWARD_BACK) != 0) { - if (DEBUG) Log.i(TAG, "Forward or back navigation"); - return true; - } - return false; - } - - /** http://crbug.com/605302 : Allow embedders to handle all pdf file downloads. */ - private boolean isInternalPdfDownload( - boolean isExternalProtocol, ExternalNavigationParams params) { - if (!isExternalProtocol && isPdfDownload(params.getUrl())) { - if (DEBUG) Log.i(TAG, "PDF downloads are now handled internally"); - return true; - } - return false; - } - - /** - * If accessing a file URL, ensure that the user has granted the necessary file access - * to the app. - */ - private boolean startFileIntentIfNecessary( - ExternalNavigationParams params, Intent targetIntent) { - if (params.getUrl().startsWith(UrlConstants.FILE_URL_SHORT_PREFIX) - && shouldRequestFileAccess(params.getUrl())) { - startFileIntent(targetIntent, params.getReferrerUrl(), - params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent()); - if (DEBUG) Log.i(TAG, "Requesting filesystem access"); - return true; - } - return false; - } - - /** - * Trigger a UI affordance that will ask the user to grant file access. After the access - * has been granted or denied, continue loading the specified file URL. - * - * @param intent The intent to continue loading the file URL. - * @param referrerUrl The HTTP referrer URL. - * @param needsToCloseTab Whether this action should close the current tab. - */ - @VisibleForTesting - protected void startFileIntent( - final Intent intent, final String referrerUrl, final boolean needsToCloseTab) { - PermissionCallback permissionCallback = new PermissionCallback() { - @Override - public void onRequestPermissionsResult(String[] permissions, int[] grantResults) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED - && mDelegate.hasValidTab()) { - loadUrlFromIntent(referrerUrl, intent.getDataString(), null, mDelegate, - needsToCloseTab, mDelegate.isIncognito()); - } else { - // TODO(tedchoc): Show an indication to the user that the navigation failed - // instead of silently dropping it on the floor. - if (needsToCloseTab) { - // If the access was not granted, then close the tab if necessary. - mDelegate.closeTab(); - } - } - } - }; - if (!mDelegate.hasValidTab()) return; - mDelegate.getWindowAndroid().requestPermissions( - new String[] {permission.READ_EXTERNAL_STORAGE}, permissionCallback); - } - - /** - * Clobber the current tab and try not to pass an intent when it should be handled internally - * so that we can deliver HTTP referrer information safely. - * - * @param url The new URL after clobbering the current tab. - * @param referrerUrl The HTTP referrer URL. - * @return OverrideUrlLoadingResult (if the tab has been clobbered, or we're launching an - * intent.) - */ - @VisibleForTesting - protected @OverrideUrlLoadingResult int clobberCurrentTab(String url, String referrerUrl) { - int transitionType = PageTransition.LINK; - final LoadUrlParams loadUrlParams = new LoadUrlParams(url, transitionType); - if (!TextUtils.isEmpty(referrerUrl)) { - Referrer referrer = new Referrer(referrerUrl, ReferrerPolicy.ALWAYS); - loadUrlParams.setReferrer(referrer); - } - if (mDelegate.hasValidTab()) { - // Loading URL will start a new navigation which cancels the current one - // that this clobbering is being done for. It leads to UAF. To avoid that, - // we're loading URL asynchronously. See https://crbug.com/732260. - PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { - @Override - public void run() { - mDelegate.loadUrlIfPossible(loadUrlParams); - } - }); - return OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB; - } else { - assert false : "clobberCurrentTab was called with an empty tab."; - Uri uri = Uri.parse(url); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - String packageName = ContextUtils.getApplicationContext().getPackageName(); - intent.putExtra(Browser.EXTRA_APPLICATION_ID, packageName); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setPackage(packageName); - startActivity(intent, false, mDelegate); - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - } - - private static void loadUrlWithReferrer( - final String url, final String referrerUrl, ExternalNavigationDelegate delegate) { - LoadUrlParams loadUrlParams = new LoadUrlParams(url, PageTransition.AUTO_TOPLEVEL); - if (!TextUtils.isEmpty(referrerUrl)) { - Referrer referrer = new Referrer(referrerUrl, ReferrerPolicy.ALWAYS); - loadUrlParams.setReferrer(referrer); - } - delegate.loadUrlIfPossible(loadUrlParams); - } - - /** - * Loads the URL from an intent, either in the current tab or a new tab, falling back to the - * |alternateUrl| if the |primaryUrl| is unsupported. - * - * Handling is determined as follows: - * - * If the url scheme is not supported we do nothing. - * If the url can be loaded in the current tab then we load the url there. - * If the url can't be loaded in the current tab then we launch a new tab and load it there. - * - * @param referrerUrl The string containing the original url from where the intent was referred. - * @param primaryUrl The primary url to load. - * @param alternateUrl The fallback url to use if the primary url is null or invalid. - * @param delegate The delegate instance with this request is associated. - * @param launchIncognito Whether the url should be loaded in an incognito tab. - * @return true if the url is loaded in the current tab. - */ - public static boolean loadUrlFromIntent(String referrerUrl, String primaryUrl, - String alternateUrl, ExternalNavigationDelegate delegate, boolean needsToCloseTab, - boolean launchIncognito) { - // Check whether we should load this URL in the current tab or in a new tab. - if (!delegate.supportsCreatingNewTabs() && !delegate.canLoadUrlInCurrentTab()) return false; - boolean loadInNewTab = delegate.supportsCreatingNewTabs() - && (!delegate.canLoadUrlInCurrentTab() || needsToCloseTab); - - boolean isPrimaryUrlValid = - (primaryUrl != null) ? UrlUtilities.isAcceptedScheme(primaryUrl) : false; - boolean isAlternateUrlValid = - (alternateUrl != null) ? UrlUtilities.isAcceptedScheme(alternateUrl) : false; - - if (!isPrimaryUrlValid && !isAlternateUrlValid) return false; - - String url = (isPrimaryUrlValid) ? primaryUrl : alternateUrl; - - if (loadInNewTab) { - delegate.loadUrlInNewTab(url, launchIncognito); - // Explicit request to close the tab. - if (needsToCloseTab) delegate.closeTab(); - return false; - } - - loadUrlWithReferrer(url, referrerUrl, delegate); - return true; - } - - private boolean isTypedRedirectToExternalProtocol( - ExternalNavigationParams params, int pageTransitionCore, boolean isExternalProtocol) { - boolean isTyped = (pageTransitionCore == PageTransition.TYPED) - || ((params.getPageTransition() & PageTransition.FROM_ADDRESS_BAR) != 0); - return isTyped && params.isRedirect() && isExternalProtocol; - } - - /** - * http://crbug.com/659301: Don't stay in Chrome for Custom Tabs redirecting to Instant Apps. - */ - private boolean handleCCTRedirectsToInstantApps(ExternalNavigationParams params, - boolean isExternalProtocol, boolean incomingIntentRedirect) { - RedirectHandler handler = params.getRedirectHandler(); - if (handler == null) return false; - if (handler.isFromCustomTabIntent() && !isExternalProtocol && incomingIntentRedirect - && !handler.shouldNavigationTypeStayInApp() - && mDelegate.maybeLaunchInstantApp( - params.getUrl(), params.getReferrerUrl(), true, isSerpReferrer())) { - if (DEBUG) { - Log.i(TAG, "Launching redirect to an instant app"); - } - return true; - } - return false; - } - - private boolean redirectShouldStayInApp( - ExternalNavigationParams params, boolean isExternalProtocol, Intent targetIntent) { - RedirectHandler handler = params.getRedirectHandler(); - if (handler == null) return false; - boolean shouldStayInApp = handler.shouldStayInApp( - isExternalProtocol, mDelegate.isIntentForTrustedCallingApp(targetIntent)); - if (shouldStayInApp || handler.shouldNotOverrideUrlLoading()) { - if (DEBUG) Log.i(TAG, "RedirectHandler decision"); - return true; - } - return false; - } - - /** Wrapper of check against the feature to support overriding for testing. */ - @VisibleForTesting - boolean blockExternalFormRedirectsWithoutGesture() { - return ExternalIntentsFeatureList.isEnabled( - ExternalIntentsFeatureList.INTENT_BLOCK_EXTERNAL_FORM_REDIRECT_NO_GESTURE); - } - - /** - * http://crbug.com/149218: We want to show the intent picker for ordinary links, providing - * the link is not an incoming intent from another application, unless it's a redirect. - */ - private boolean preferToShowIntentPicker(ExternalNavigationParams params, - int pageTransitionCore, boolean isExternalProtocol, boolean isFormSubmit, - boolean linkNotFromIntent, boolean incomingIntentRedirect) { - // http://crbug.com/169549 : If you type in a URL that then redirects in server side to a - // link that cannot be rendered by the browser, we want to show the intent picker. - if (isTypedRedirectToExternalProtocol(params, pageTransitionCore, isExternalProtocol)) { - return true; - } - // http://crbug.com/181186: We need to show the intent picker when we receive a redirect - // following a form submit. - boolean isRedirectFromFormSubmit = isFormSubmit && params.isRedirect(); - - if (!linkNotFromIntent && !incomingIntentRedirect && !isRedirectFromFormSubmit) { - if (DEBUG) Log.i(TAG, "Incoming intent (not a redirect)"); - return false; - } - // http://crbug.com/839751: Require user gestures for form submits to external - // protocols. - // TODO(tedchoc): Turn this on by default once we verify this change does - // not break the world. - if (isRedirectFromFormSubmit && !incomingIntentRedirect && !params.hasUserGesture() - && blockExternalFormRedirectsWithoutGesture()) { - if (DEBUG) { - Log.i(TAG, - "Incoming form intent attempting to redirect without " - + "user gesture"); - } - return false; - } - // http://crbug/331571 : Do not override a navigation started from user typing. - if (params.getRedirectHandler() != null - && params.getRedirectHandler().isNavigationFromUserTyping()) { - if (DEBUG) Log.i(TAG, "Navigation from user typing"); - return false; - } - return true; - } - - /** - * http://crbug.com/159153: Don't override navigation from a chrome:* url to http or https. For - * example when clicking a link in bookmarks or most visited. When navigating from such a page, - * there is clear intent to complete the navigation in Chrome. - */ - private boolean isLinkFromChromeInternalPage(ExternalNavigationParams params) { - if (params.getReferrerUrl() == null) return false; - if (params.getReferrerUrl().startsWith(UrlConstants.CHROME_URL_PREFIX) - && (params.getUrl().startsWith(UrlConstants.HTTP_URL_PREFIX) - || params.getUrl().startsWith(UrlConstants.HTTPS_URL_PREFIX))) { - if (DEBUG) Log.i(TAG, "Link from an internal chrome:// page"); - return true; - } - return false; - } - - private boolean handleWtaiMcProtocol(ExternalNavigationParams params) { - if (!params.getUrl().startsWith(WTAI_MC_URL_PREFIX)) return false; - // wtai://wp/mc;number - // number=string(phone-number) - startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse(WebView.SCHEME_TEL - + params.getUrl().substring(WTAI_MC_URL_PREFIX.length()))), - false, mDelegate); - if (DEBUG) Log.i(TAG, "wtai:// link handled"); - RecordUserAction.record("Android.PhoneIntent"); - return true; - } - - private boolean isUnhandledWtaiProtocol(ExternalNavigationParams params) { - if (!params.getUrl().startsWith(WTAI_URL_PREFIX)) return false; - if (DEBUG) Log.i(TAG, "Unsupported wtai:// link"); - return true; - } - - /** - * The "about:", "chrome:", "chrome-native:", and "devtools:" schemes - * are internal to the browser; don't want these to be dispatched to other apps. - */ - private boolean hasInternalScheme( - ExternalNavigationParams params, Intent targetIntent, boolean hasIntentScheme) { - String url; - if (hasIntentScheme) { - // TODO(https://crbug.com/783819): When this function is converted to GURL, we should - // also call fixUpUrl on this user-provided URL as the fixed-up URL is what we would end - // up navigating to. - url = targetIntent.getDataString(); - if (url == null) return false; - } else { - url = params.getUrl(); - } - if (url.startsWith(ContentUrlConstants.ABOUT_SCHEME) - || url.startsWith(UrlConstants.CHROME_URL_SHORT_PREFIX) - || url.startsWith(UrlConstants.CHROME_NATIVE_URL_SHORT_PREFIX) - || url.startsWith(UrlConstants.DEVTOOLS_URL_SHORT_PREFIX)) { - if (DEBUG) Log.i(TAG, "Navigating to a chrome-internal page"); - return true; - } - return false; - } - - /** The "content:" scheme is disabled in Clank. Do not try to start an activity. */ - private boolean hasContentScheme( - ExternalNavigationParams params, Intent targetIntent, boolean hasIntentScheme) { - String url; - if (hasIntentScheme) { - url = targetIntent.getDataString(); - if (url == null) return false; - } else { - url = params.getUrl(); - } - if (!url.startsWith(UrlConstants.CONTENT_URL_SHORT_PREFIX)) return false; - if (DEBUG) Log.i(TAG, "Navigation to content: URL"); - return true; - } - - /** - * Intent URIs leads to creating intents that chrome would use for firing external navigations - * via Android. Android throws an exception [1] when an application exposes a file:// Uri to - * another app. - * - * This method checks if the |targetIntent| contains the file:// scheme in its data. - * - * [1]: https://developer.android.com/reference/android/os/FileUriExposedException - */ - private boolean hasFileSchemeInIntentURI(Intent targetIntent, boolean hasIntentScheme) { - // We are only concerned with targetIntent that was generated due to intent:// schemes only. - if (!hasIntentScheme) return false; - - Uri data = targetIntent.getData(); - - if (data == null || data.getScheme() == null) return false; - - if (data.getScheme().equalsIgnoreCase(UrlConstants.FILE_SCHEME)) { - if (DEBUG) Log.i(TAG, "Intent navigation to file: URI"); - return true; - } - return false; - } - - /** - * Special case - It makes no sense to use an external application for a YouTube - * pairing code URL, since these match the current tab with a device (Chromecast - * or similar) it is supposed to be controlling. Using a different application - * that isn't expecting this (in particular YouTube) doesn't work. - */ - private boolean isYoutubePairingCode(ExternalNavigationParams params) { - // TODO(https://crbug.com/1009539): Replace this regex with proper URI parsing. - if (params.getUrl().matches(".*youtube\\.com(\\/.*)?\\?(.+&)?pairingCode=[^&].+")) { - if (DEBUG) Log.i(TAG, "YouTube URL with a pairing code"); - return true; - } - return false; - } - - private boolean externalIntentRequestsDisabledForUrl(ExternalNavigationParams params) { - // TODO(changwan): check if we need to handle URL even when external intent is off. - if (CommandLine.getInstance().hasSwitch( - ExternalIntentsSwitches.DISABLE_EXTERNAL_INTENT_REQUESTS)) { - Log.w(TAG, "External intent handling is disabled by a command-line flag."); - return true; - } - - if (mDelegate.shouldDisableExternalIntentRequestsForUrl(params.getUrl())) { - if (DEBUG) Log.i(TAG, "Delegate disables external intent requests for URL."); - return true; - } - return false; - } - - /** - * If the intent can't be resolved, we should fall back to the browserFallbackUrl, or try to - * find the app on the market if no fallback is provided. - */ - private int handleUnresolvableIntent( - ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) { - // Fallback URL will be handled by the caller of shouldOverrideUrlLoadingInternal. - if (browserFallbackUrl != null) return OverrideUrlLoadingResult.NO_OVERRIDE; - if (targetIntent.getPackage() != null) return handleWithMarketIntent(params, targetIntent); - - if (DEBUG) Log.i(TAG, "Could not find an external activity to use"); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - private @OverrideUrlLoadingResult int handleWithMarketIntent( - ExternalNavigationParams params, Intent intent) { - String marketReferrer = IntentUtils.safeGetStringExtra(intent, EXTRA_MARKET_REFERRER); - if (TextUtils.isEmpty(marketReferrer)) { - marketReferrer = ContextUtils.getApplicationContext().getPackageName(); - } - return sendIntentToMarket(intent.getPackage(), marketReferrer, params); - } - - private boolean maybeSetSmsPackage(Intent targetIntent) { - final Uri uri = targetIntent.getData(); - if (targetIntent.getPackage() == null && uri != null - && UrlConstants.SMS_SCHEME.equals(uri.getScheme())) { - List<ResolveInfo> resolvingInfos = queryIntentActivities(targetIntent); - targetIntent.setPackage(getDefaultSmsPackageName(resolvingInfos)); - return true; - } - return false; - } - - private void maybeRecordPhoneIntentMetrics(Intent targetIntent) { - final Uri uri = targetIntent.getData(); - if (uri != null && UrlConstants.TEL_SCHEME.equals(uri.getScheme()) - || (Intent.ACTION_DIAL.equals(targetIntent.getAction())) - || (Intent.ACTION_CALL.equals(targetIntent.getAction()))) { - RecordUserAction.record("Android.PhoneIntent"); - } - } - - /** - * In incognito mode, links that can be handled within the browser should just do so, - * without asking the user. - */ - private boolean shouldStayInIncognito( - ExternalNavigationParams params, boolean isExternalProtocol) { - if (params.isIncognito() && !isExternalProtocol) { - if (DEBUG) Log.i(TAG, "Stay incognito"); - return true; - } - return false; - } - - private boolean fallBackToHandlingWithInstantApp(ExternalNavigationParams params, - boolean incomingIntentRedirect, boolean linkNotFromIntent) { - if (incomingIntentRedirect - && mDelegate.maybeLaunchInstantApp( - params.getUrl(), params.getReferrerUrl(), true, isSerpReferrer())) { - if (DEBUG) Log.i(TAG, "Launching instant Apps redirect"); - return true; - } else if (linkNotFromIntent && !params.isIncognito() - && mDelegate.maybeLaunchInstantApp( - params.getUrl(), params.getReferrerUrl(), false, isSerpReferrer())) { - if (DEBUG) Log.i(TAG, "Launching instant Apps link"); - return true; - } - return false; - } - - /** - * This is the catch-all path for any intent that the app can handle that doesn't have a - * specialized external app handling it. - */ - private @OverrideUrlLoadingResult int fallBackToHandlingInApp() { - if (DEBUG) Log.i(TAG, "No specialized handler for URL"); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - /** - * Current URL has at least one specialized handler available. For navigations - * within the same host, keep the navigation inside the browser unless the set of - * available apps to handle the new navigation is different. http://crbug.com/463138 - */ - private boolean shouldStayWithinHost(ExternalNavigationParams params, boolean isLink, - boolean isFormSubmit, List<ResolveInfo> resolvingInfos, boolean isExternalProtocol) { - if (isExternalProtocol) return false; - - // TODO(https://crbug.com/1009539): Replace this host parsing with a UrlUtilities or GURL - // function call. - String lastCommittedUrl = getLastCommittedUrl(); - String previousUriString = - lastCommittedUrl != null ? lastCommittedUrl : params.getReferrerUrl(); - if (previousUriString == null || (!isLink && !isFormSubmit)) return false; - - URI currentUri; - URI previousUri; - - try { - currentUri = new URI(params.getUrl()); - previousUri = new URI(previousUriString); - } catch (Exception e) { - return false; - } - - if (currentUri == null || previousUri == null - || !TextUtils.equals(currentUri.getHost(), previousUri.getHost())) { - return false; - } - - Intent previousIntent; - try { - previousIntent = Intent.parseUri(previousUriString, Intent.URI_INTENT_SCHEME); - } catch (Exception e) { - return false; - } - - if (previousIntent != null - && resolversSubsetOf(resolvingInfos, queryIntentActivities(previousIntent))) { - if (DEBUG) Log.i(TAG, "Same host, no new resolvers"); - return true; - } - return false; - } - - /** - * For security reasons, we disable all intent:// URLs to Instant Apps that are not coming from - * SERP. - */ - private boolean preventDirectInstantAppsIntent( - boolean isDirectInstantAppsIntent, boolean shouldProxyForInstantApps) { - if (!isDirectInstantAppsIntent || shouldProxyForInstantApps) return false; - if (DEBUG) Log.i(TAG, "Intent URL to an Instant App"); - RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent", - AiaIntent.OTHER, AiaIntent.NUM_ENTRIES); - return true; - } - - /** - * Prepare the intent to be sent. This function does not change the filtering for the intent, - * so the list if resolveInfos for the intent will be the same before and after this function. - */ - private void prepareExternalIntent(Intent targetIntent, ExternalNavigationParams params, - List<ResolveInfo> resolvingInfos, boolean shouldProxyForInstantApps) { - // Set the Browser application ID to us in case the user chooses this app - // as the app. This will make sure the link is opened in the same tab - // instead of making a new one in the case of Chrome. - targetIntent.putExtra(Browser.EXTRA_APPLICATION_ID, - ContextUtils.getApplicationContext().getPackageName()); - if (params.isOpenInNewTab()) targetIntent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); - targetIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // Ensure intents re-target potential caller activity when we run in CCT mode. - targetIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mDelegate.maybeSetWindowId(targetIntent); - targetIntent.putExtra(EXTRA_EXTERNAL_NAV_PACKAGES, getSpecializedHandlers(resolvingInfos)); - - if (params.getReferrerUrl() != null) { - mDelegate.maybeSetPendingReferrer(targetIntent, params.getReferrerUrl()); - } - - if (params.isIncognito()) mDelegate.maybeSetPendingIncognitoUrl(targetIntent); - - mDelegate.maybeAdjustInstantAppExtras(targetIntent, shouldProxyForInstantApps); - - if (shouldProxyForInstantApps) { - RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent", - AiaIntent.SERP, AiaIntent.NUM_ENTRIES); - } - - mDelegate.maybeSetRequestMetadata(targetIntent, params.hasUserGesture(), - params.isRendererInitiated(), params.getInitiatorOrigin()); - } - - private @OverrideUrlLoadingResult int handleExternalIncognitoIntent(Intent targetIntent, - ExternalNavigationParams params, String browserFallbackUrl, - boolean shouldProxyForInstantApps) { - // This intent may leave this app. Warn the user that incognito does not carry over - // to external apps. - if (startIncognitoIntent(targetIntent, params.getReferrerUrl(), browserFallbackUrl, - params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(), - shouldProxyForInstantApps)) { - if (DEBUG) Log.i(TAG, "Incognito navigation out"); - return OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION; - } - if (DEBUG) Log.i(TAG, "Failed to show incognito alert dialog."); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - /** - * Display a dialog warning the user that they may be leaving this app by starting this - * intent. Give the user the opportunity to cancel the action. And if it is canceled, a - * navigation will happen in this app. Catches BadTokenExceptions caused by showing the dialog - * on certain devices. (crbug.com/782602) - * @param intent The intent for external application that will be sent. - * @param referrerUrl The referrer for the current navigation. - * @param fallbackUrl The URL to load if the user doesn't proceed with external intent. - * @param needsToCloseTab Whether the current tab has to be closed after the intent is sent. - * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is - * used by Instant Apps intents. - * @return True if the function returned error free, false if it threw an exception. - */ - private boolean startIncognitoIntent(final Intent intent, final String referrerUrl, - final String fallbackUrl, final boolean needsToCloseTab, final boolean proxy) { - try { - return startIncognitoIntentInternal( - intent, referrerUrl, fallbackUrl, needsToCloseTab, proxy); - } catch (BadTokenException e) { - return false; - } - } - - /** - * Internal implementation of startIncognitoIntent(), with all the same parameters. - */ - @VisibleForTesting - protected boolean startIncognitoIntentInternal(final Intent intent, final String referrerUrl, - final String fallbackUrl, final boolean needsToCloseTab, final boolean proxy) { - if (!mDelegate.hasValidTab()) return false; - Context context = mDelegate.getContext(); - if (ContextUtils.activityFromContext(context) == null) return false; - - new UiUtils.CompatibleAlertDialogBuilder(context, R.style.Theme_Chromium_AlertDialog) - .setTitle(R.string.external_app_leave_incognito_warning_title) - .setMessage(R.string.external_app_leave_incognito_warning) - .setPositiveButton(R.string.external_app_leave_incognito_leave, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try { - startActivity(intent, proxy, mDelegate); - if (mDelegate.canCloseTabOnIncognitoIntentLaunch() - && needsToCloseTab) { - mDelegate.closeTab(); - } - } catch (ActivityNotFoundException e) { - // The activity that we thought was going to handle the intent - // no longer exists, so catch the exception and assume Chrome - // can handle it. - loadUrlFromIntent(referrerUrl, fallbackUrl, - intent.getDataString(), mDelegate, needsToCloseTab, - true); - } - } - }) - .setNegativeButton(R.string.external_app_leave_incognito_stay, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - loadUrlFromIntent(referrerUrl, fallbackUrl, intent.getDataString(), - mDelegate, needsToCloseTab, true); - } - }) - .setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - loadUrlFromIntent(referrerUrl, fallbackUrl, intent.getDataString(), - mDelegate, needsToCloseTab, true); - } - }) - .show(); - return true; - } - - /** - * If some third-party app launched this app with an intent, and the URL got redirected, and the - * user explicitly chose this app over other intent handlers, stay in the app unless there was a - * new intent handler after redirection or the app cannot handle it internally any more. - * Custom tabs are an exception to this rule, since at no point, the user sees an intent picker - * and "picking the Chrome app" is handled inside the support library. - */ - private boolean shouldKeepIntentRedirectInApp(ExternalNavigationParams params, - boolean incomingIntentRedirect, List<ResolveInfo> resolvingInfos, - boolean isExternalProtocol) { - if (params.getRedirectHandler() != null && incomingIntentRedirect && !isExternalProtocol - && !params.getRedirectHandler().isFromCustomTabIntent() - && !params.getRedirectHandler().hasNewResolver(resolvingInfos)) { - if (DEBUG) Log.i(TAG, "Custom tab redirect no handled"); - return true; - } - return false; - } - - /** - * @param packageName The package to check. - * @return Whether the package is a valid WebAPK package. - */ - @VisibleForTesting - protected boolean isValidWebApk(String packageName) { - // Ensure that WebApkValidator is initialized (note: this method is a no-op after the first - // time that it is invoked). - WebApkValidator.init( - ChromeWebApkHostSignature.EXPECTED_SIGNATURE, ChromeWebApkHostSignature.PUBLIC_KEY); - return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName); - } - - /** - * Returns whether the activity belongs to a WebAPK and the URL is within the scope of the - * WebAPK. The WebAPK's main activity is a bouncer that redirects to the WebAPK Activity in - * Chrome. In order to avoid bouncing indefinitely, we should not override the navigation if we - * are currently showing the WebAPK (params#nativeClientPackageName()) that we will redirect to. - */ - private boolean isAlreadyInTargetWebApk( - List<ResolveInfo> resolveInfos, ExternalNavigationParams params) { - String currentName = params.nativeClientPackageName(); - if (currentName == null) return false; - for (ResolveInfo resolveInfo : resolveInfos) { - ActivityInfo info = resolveInfo.activityInfo; - if (info != null && currentName.equals(info.packageName)) { - if (DEBUG) Log.i(TAG, "Already in WebAPK"); - return true; - } - } - return false; - } - - private boolean launchExternalIntent(Intent targetIntent, boolean shouldProxyForInstantApps) { - try { - if (!startActivityIfNeeded(targetIntent, shouldProxyForInstantApps)) { - if (DEBUG) Log.i(TAG, "The current Activity was the only targeted Activity."); - return false; - } - } catch (ActivityNotFoundException e) { - // The targeted app must have been uninstalled/disabled since we queried for Activities - // to handle this intent. - if (DEBUG) Log.i(TAG, "Activity not found."); - return false; - } - if (DEBUG) Log.i(TAG, "startActivityIfNeeded"); - return true; - } - - // This will handle external navigations only for intent meant for Autofill Assistant. - private boolean handleWithAutofillAssistant( - ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) { - if (mDelegate.isIntentToAutofillAssistant(targetIntent)) { - if (mDelegate.handleWithAutofillAssistant( - params, targetIntent, browserFallbackUrl, isGoogleReferrer())) { - if (DEBUG) Log.i(TAG, "Handled with Autofill Assistant."); - } else { - if (DEBUG) Log.i(TAG, "Not handled with Autofill Assistant."); - } - return true; - } - return false; - } - - // Check if we're navigating under conditions that should never launch an external app. - private boolean shouldBlockAllExternalAppLaunches(ExternalNavigationParams params) { - return blockExternalNavFromAutoSubframe(params) || blockExternalNavWhileBackgrounded(params) - || blockExternalNavFromBackgroundTab(params) || ignoreBackForwardNav(params); - } - - private @OverrideUrlLoadingResult int shouldOverrideUrlLoadingInternal( - ExternalNavigationParams params, Intent targetIntent, - @Nullable String browserFallbackUrl, MutableBoolean canLaunchExternalFallbackResult) { - sanitizeQueryIntentActivitiesIntent(targetIntent); - // Don't allow external fallback URLs by default. - canLaunchExternalFallbackResult.set(false); - - if (shouldBlockAllExternalAppLaunches(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; - - if (handleWithAutofillAssistant(params, targetIntent, browserFallbackUrl)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(params.getUrl()); - - if (isInternalPdfDownload(isExternalProtocol, params)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - // This check should happen for reloads, navigations, etc..., which is why - // it occurs before the subsequent blocks. - if (startFileIntentIfNecessary(params, targetIntent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION; - } - - // This should come after file intents, but before any returns of - // OVERRIDE_WITH_EXTERNAL_INTENT. - if (externalIntentRequestsDisabledForUrl(params)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - int pageTransitionCore = params.getPageTransition() & PageTransition.CORE_MASK; - boolean isLink = pageTransitionCore == PageTransition.LINK; - boolean isFormSubmit = pageTransitionCore == PageTransition.FORM_SUBMIT; - boolean isFromIntent = (params.getPageTransition() & PageTransition.FROM_API) != 0; - boolean linkNotFromIntent = isLink && !isFromIntent; - - boolean isOnEffectiveIntentRedirect = params.getRedirectHandler() == null - ? false - : params.getRedirectHandler().isOnEffectiveIntentRedirectChain(); - - // http://crbug.com/170925: We need to show the intent picker when we receive an intent from - // another app that 30x redirects to a YouTube/Google Maps/Play Store/Google+ URL etc. - boolean incomingIntentRedirect = - (isLink && isFromIntent && params.isRedirect()) || isOnEffectiveIntentRedirect; - - if (handleCCTRedirectsToInstantApps(params, isExternalProtocol, incomingIntentRedirect)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } else if (redirectShouldStayInApp(params, isExternalProtocol, targetIntent)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (!preferToShowIntentPicker(params, pageTransitionCore, isExternalProtocol, isFormSubmit, - linkNotFromIntent, incomingIntentRedirect)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (isLinkFromChromeInternalPage(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; - - if (handleWtaiMcProtocol(params)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - // TODO: handle other WTAI schemes. - if (isUnhandledWtaiProtocol(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; - - boolean hasIntentScheme = params.getUrl().startsWith(UrlConstants.INTENT_URL_SHORT_PREFIX) - || params.getUrl().startsWith(UrlConstants.APP_INTENT_URL_SHORT_PREFIX); - if (hasInternalScheme(params, targetIntent, hasIntentScheme)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (hasContentScheme(params, targetIntent, hasIntentScheme)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (hasFileSchemeInIntentURI(targetIntent, hasIntentScheme)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (isYoutubePairingCode(params)) return OverrideUrlLoadingResult.NO_OVERRIDE; - - if (shouldStayInIncognito(params, isExternalProtocol)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (!maybeSetSmsPackage(targetIntent)) maybeRecordPhoneIntentMetrics(targetIntent); - - if (hasIntentScheme) recordIntentActionMetrics(targetIntent); - - // From this point on, we have determined it is safe to launch an External App from a - // fallback URL, provided the user isn't in incognito. - if (!params.isIncognito()) canLaunchExternalFallbackResult.set(true); - - Intent debugIntent = new Intent(targetIntent); - List<ResolveInfo> resolvingInfos = queryIntentActivities(targetIntent); - if (resolvingInfos.isEmpty()) { - return handleUnresolvableIntent(params, targetIntent, browserFallbackUrl); - } - - if (browserFallbackUrl != null) targetIntent.removeExtra(EXTRA_BROWSER_FALLBACK_URL); - - boolean hasSpecializedHandler = countSpecializedHandlers(resolvingInfos) > 0; - if (!isExternalProtocol && !hasSpecializedHandler) { - if (fallBackToHandlingWithInstantApp( - params, incomingIntentRedirect, linkNotFromIntent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - return fallBackToHandlingInApp(); - } - - // From this point on we should only have intents that this app can't handle, or intents for - // apps with specialized handlers. - - if (shouldStayWithinHost( - params, isLink, isFormSubmit, resolvingInfos, isExternalProtocol)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - boolean isDirectInstantAppsIntent = - isExternalProtocol && mDelegate.isIntentToInstantApp(targetIntent); - boolean shouldProxyForInstantApps = isDirectInstantAppsIntent && isSerpReferrer(); - if (preventDirectInstantAppsIntent(isDirectInstantAppsIntent, shouldProxyForInstantApps)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - prepareExternalIntent(targetIntent, params, resolvingInfos, shouldProxyForInstantApps); - // As long as our intent resolution hasn't changed, resolvingInfos won't need to be - // re-computed as it won't have changed. - assert intentResolutionMatches(debugIntent, targetIntent); - - if (params.isIncognito()) { - boolean intentTargetedToApp = mDelegate.willAppHandleIntent(targetIntent); - - // The user is about to potentially leave the app, so we should ask whether they want to - // leave incognito or not. - if (!intentTargetedToApp) { - return handleExternalIncognitoIntent( - targetIntent, params, browserFallbackUrl, shouldProxyForInstantApps); - } - - // The intent is staying in the app, so we can simply navigate to the intent's URL, - // while staying in incognito. - return mDelegate.handleIncognitoIntentTargetingSelf( - targetIntent, params.getReferrerUrl(), browserFallbackUrl); - } - - if (shouldKeepIntentRedirectInApp( - params, incomingIntentRedirect, resolvingInfos, isExternalProtocol)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (isAlreadyInTargetWebApk(resolvingInfos, params)) { - return OverrideUrlLoadingResult.NO_OVERRIDE; - } else if (launchWebApkIfSoleIntentHandler(resolvingInfos, targetIntent)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - if (launchExternalIntent(targetIntent, shouldProxyForInstantApps)) { - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - /** - * Sanitize intent to be passed to {@link queryIntentActivities()} - * ensuring that web pages cannot bypass browser security. - */ - private void sanitizeQueryIntentActivitiesIntent(Intent intent) { - intent.setFlags(intent.getFlags() & ALLOWED_INTENT_FLAGS); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setComponent(null); - Intent selector = intent.getSelector(); - if (selector != null) { - selector.addCategory(Intent.CATEGORY_BROWSABLE); - selector.setComponent(null); - } - } - - /** - * @return OVERRIDE_WITH_EXTERNAL_INTENT when we successfully started market activity, - * NO_OVERRIDE otherwise. - */ - private @OverrideUrlLoadingResult int sendIntentToMarket( - String packageName, String marketReferrer, ExternalNavigationParams params) { - Uri marketUri = - new Uri.Builder() - .scheme("market") - .authority("details") - .appendQueryParameter(PLAY_PACKAGE_PARAM, packageName) - .appendQueryParameter(PLAY_REFERRER_PARAM, Uri.decode(marketReferrer)) - .build(); - Intent intent = new Intent(Intent.ACTION_VIEW, marketUri); - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setPackage("com.android.vending"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (params.getReferrerUrl() != null) { - intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(params.getReferrerUrl())); - } - - if (!deviceCanHandleIntent(intent)) { - // Exit early if the Play Store isn't available. (https://crbug.com/820709) - if (DEBUG) Log.i(TAG, "Play Store not installed."); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - - if (params.isIncognito()) { - if (!startIncognitoIntent(intent, params.getReferrerUrl(), null, - - params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(), false)) { - if (DEBUG) Log.i(TAG, "Failed to show incognito alert dialog."); - return OverrideUrlLoadingResult.NO_OVERRIDE; - } - if (DEBUG) Log.i(TAG, "Incognito intent to Play Store."); - return OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION; - } else { - startActivity(intent, false, mDelegate); - if (DEBUG) Log.i(TAG, "Intent to Play Store."); - return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT; - } - } - - /** - * If the given URL is to Google Play, extracts the package name and referrer tracking code - * from the {@param url} and returns as a Pair in that order. Otherwise returns null. - */ - private Pair<String, String> maybeGetPlayStoreAppIdAndReferrer(String url) { - Uri uri = Uri.parse(url); - if (PLAY_HOSTNAME.equals(uri.getHost()) && uri.getPath() != null - && uri.getPath().startsWith(PLAY_APP_PATH) - && !TextUtils.isEmpty(uri.getQueryParameter(PLAY_PACKAGE_PARAM))) { - return new Pair<String, String>(uri.getQueryParameter(PLAY_PACKAGE_PARAM), - uri.getQueryParameter(PLAY_REFERRER_PARAM)); - } - return null; - } - - /** - * @return Whether the |url| could be handled by an external application on the system. - */ - @VisibleForTesting - boolean canExternalAppHandleUrl(String url) { - if (url.startsWith(WTAI_MC_URL_PREFIX)) return true; - Intent intent; - try { - intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException ex) { - // Ignore the error. - Log.w(TAG, "Bad URI %s", url, ex); - return false; - } - if (intent.getPackage() != null) return true; - - List<ResolveInfo> resolvingInfos = queryIntentActivities(intent); - return resolvingInfos != null && !resolvingInfos.isEmpty(); - } - - /** - * Dispatch SMS intents to the default SMS application if applicable. - * Most SMS apps refuse to send SMS if not set as default SMS application. - * - * @param resolvingComponentNames The list of ComponentName that resolves the current intent. - */ - private String getDefaultSmsPackageName(List<ResolveInfo> resolvingComponentNames) { - String defaultSmsPackageName = getDefaultSmsPackageNameFromSystem(); - if (defaultSmsPackageName == null) return null; - // Makes sure that the default SMS app actually resolves the intent. - for (ResolveInfo resolveInfo : resolvingComponentNames) { - if (defaultSmsPackageName.equals(resolveInfo.activityInfo.packageName)) { - return defaultSmsPackageName; - } - } - return null; - } - - /** - * Launches WebAPK if the WebAPK is the sole non-browser handler for the given intent. - * @return Whether a WebAPK was launched. - */ - private boolean launchWebApkIfSoleIntentHandler( - List<ResolveInfo> resolvingInfos, Intent targetIntent) { - ArrayList<String> packages = getSpecializedHandlers(resolvingInfos); - if (packages.size() != 1 || !isValidWebApk(packages.get(0))) return false; - Intent webApkIntent = new Intent(targetIntent); - webApkIntent.setPackage(packages.get(0)); - try { - startActivity(webApkIntent, false, mDelegate); - if (DEBUG) Log.i(TAG, "Launched WebAPK"); - return true; - } catch (ActivityNotFoundException e) { - // The WebApk must have been uninstalled/disabled since we queried for Activities to - // handle this intent. - if (DEBUG) Log.i(TAG, "WebAPK launch failed"); - return false; - } - } - - /** - * Returns whether or not there's an activity available to handle the intent. - */ - private boolean deviceCanHandleIntent(Intent intent) { - List<ResolveInfo> resolveInfos = queryIntentActivities(intent); - return resolveInfos != null && !resolveInfos.isEmpty(); - } - - /** - * See {@link PackageManagerUtils#queryIntentActivities(Intent, int)} - */ - @NonNull - private List<ResolveInfo> queryIntentActivities(Intent intent) { - return PackageManagerUtils.queryIntentActivities( - intent, PackageManager.GET_RESOLVED_FILTER); - } - - private static boolean intentResolutionMatches(Intent intent, Intent other) { - return intent.filterEquals(other) - && (intent.getSelector() == other.getSelector() - || intent.getSelector().filterEquals(other.getSelector())); - } - - /** - * @return Whether the URL is a file download. - */ - @VisibleForTesting - boolean isPdfDownload(String url) { - String fileExtension = MimeTypeMap.getFileExtensionFromUrl(url); - if (TextUtils.isEmpty(fileExtension)) return false; - - return PDF_EXTENSION.equals(fileExtension); - } - - private static boolean isPdfIntent(Intent intent) { - if (intent == null || intent.getData() == null) return false; - String filename = intent.getData().getLastPathSegment(); - return (filename != null && filename.endsWith(PDF_SUFFIX)) - || PDF_MIME.equals(intent.getType()); - } - - /** - * Records the dispatching of an external intent. - */ - private static void recordExternalNavigationDispatched(Intent intent) { - ArrayList<String> specializedHandlers = - intent.getStringArrayListExtra(EXTRA_EXTERNAL_NAV_PACKAGES); - if (specializedHandlers != null && specializedHandlers.size() > 0) { - RecordUserAction.record("MobileExternalNavigationDispatched"); - } - } - - /** - * If the intent is for a pdf, resolves intent handlers to find the platform pdf viewer if - * it is available and force is for the provided |intent| so that the user doesn't need to - * choose it from Intent picker. - * - * @param intent Intent to open. - */ - private static void forcePdfViewerAsIntentHandlerIfNeeded(Intent intent) { - if (intent == null || !isPdfIntent(intent)) return; - resolveIntent(intent, true /* allowSelfOpen (ignored) */); - } - - /** - * Retrieve the best activity for the given intent. If a default activity is provided, - * choose the default one. Otherwise, return the Intent picker if there are more than one - * capable activities. If the intent is pdf type, return the platform pdf viewer if - * it is available so user don't need to choose it from Intent picker. - * - * @param intent Intent to open. - * @param allowSelfOpen Whether chrome itself is allowed to open the intent. - * @return true if the intent can be resolved, or false otherwise. - */ - public static boolean resolveIntent(Intent intent, boolean allowSelfOpen) { - Context context = ContextUtils.getApplicationContext(); - ResolveInfo info = PackageManagerUtils.resolveActivity(intent, 0); - if (info == null) return false; - - final String packageName = context.getPackageName(); - if (info.match != 0) { - // There is a default activity for this intent, use that. - return allowSelfOpen || !packageName.equals(info.activityInfo.packageName); - } - List<ResolveInfo> handlers = PackageManagerUtils.queryIntentActivities( - intent, PackageManager.MATCH_DEFAULT_ONLY); - if (handlers == null || handlers.isEmpty()) return false; - boolean canSelfOpen = false; - boolean hasPdfViewer = false; - for (ResolveInfo resolveInfo : handlers) { - String pName = resolveInfo.activityInfo.packageName; - if (packageName.equals(pName)) { - canSelfOpen = true; - } else if (PDF_VIEWER.equals(pName)) { - if (isPdfIntent(intent)) { - intent.setClassName(pName, resolveInfo.activityInfo.name); - Uri referrer = new Uri.Builder() - .scheme(IntentUtils.ANDROID_APP_REFERRER_SCHEME) - .authority(packageName) - .build(); - intent.putExtra(Intent.EXTRA_REFERRER, referrer); - hasPdfViewer = true; - break; - } - } - } - return !canSelfOpen || allowSelfOpen || hasPdfViewer; - } - - /** - * Start an activity for the intent. Used for intents that must be handled externally. - * @param intent The intent we want to send. - * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is - * used by Instant Apps intents). - */ - public static void startActivity( - Intent intent, boolean proxy, ExternalNavigationDelegate delegate) { - try { - forcePdfViewerAsIntentHandlerIfNeeded(intent); - if (proxy) { - delegate.dispatchAuthenticatedIntent(intent); - } else { - // Start the activity via the current activity if possible, and otherwise as a new - // task from the application context. - Context context = ContextUtils.activityFromContext(delegate.getContext()); - if (context == null) { - context = ContextUtils.getApplicationContext(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - context.startActivity(intent); - } - recordExternalNavigationDispatched(intent); - } catch (RuntimeException e) { - IntentUtils.logTransactionTooLargeOrRethrow(e, intent); - } - - delegate.didStartActivity(intent); - } - - /** - * Start an activity for the intent. Used for intents that may be handled internally or - * externally. - * @param intent The intent we want to send. - * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is - * used by Instant Apps intents). - * @returns whether an activity was started for the intent. - */ - private boolean startActivityIfNeeded(Intent intent, boolean proxy) { - @ExternalNavigationDelegate.StartActivityIfNeededResult - int delegateResult = mDelegate.maybeHandleStartActivityIfNeeded(intent, proxy); - - switch (delegateResult) { - case ExternalNavigationDelegate.StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START: - return true; - case ExternalNavigationDelegate.StartActivityIfNeededResult - .HANDLED_WITHOUT_ACTIVITY_START: - return false; - case ExternalNavigationDelegate.StartActivityIfNeededResult.DID_NOT_HANDLE: - return startActivityIfNeededInternal(intent, proxy); - } - - assert false; - return false; - } - - /** - * Implementation of startActivityIfNeeded() that is used when the delegate does not handle the - * event. - */ - private boolean startActivityIfNeededInternal(Intent intent, boolean proxy) { - boolean activityWasLaunched; - // Only touches disk on Kitkat. See http://crbug.com/617725 for more context. - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); - try { - forcePdfViewerAsIntentHandlerIfNeeded(intent); - if (proxy) { - mDelegate.dispatchAuthenticatedIntent(intent); - activityWasLaunched = true; - } else { - Activity activity = ContextUtils.activityFromContext(mDelegate.getContext()); - if (activity != null) { - activityWasLaunched = activity.startActivityIfNeeded(intent, -1); - } else { - activityWasLaunched = false; - } - } - if (activityWasLaunched) { - recordExternalNavigationDispatched(intent); - } - return activityWasLaunched; - } catch (SecurityException e) { - // https://crbug.com/808494: Handle the URL internally if dispatching to another - // application fails with a SecurityException. This happens due to malformed manifests - // in another app. - return false; - } catch (RuntimeException e) { - IntentUtils.logTransactionTooLargeOrRethrow(e, intent); - return false; - } finally { - StrictMode.setThreadPolicy(oldPolicy); - } - } - - /** - * Returns the number of specialized intent handlers in {@params infos}. Specialized intent - * handlers are intent handlers which handle only a few URLs (e.g. google maps or youtube). - */ - private int countSpecializedHandlers(List<ResolveInfo> infos) { - return getSpecializedHandlersWithFilter( - infos, null, mDelegate.handlesInstantAppLaunchingInternally()) - .size(); - } - - /** - * Returns the subset of {@params infos} that are specialized intent handlers. - */ - private ArrayList<String> getSpecializedHandlers(List<ResolveInfo> infos) { - return getSpecializedHandlersWithFilter( - infos, null, mDelegate.handlesInstantAppLaunchingInternally()); - } - - private static boolean matchResolveInfoExceptWildCardHost( - ResolveInfo info, String filterPackageName) { - IntentFilter intentFilter = info.filter; - if (intentFilter == null) { - // Error on the side of classifying ResolveInfo as generic. - return false; - } - if (intentFilter.countDataAuthorities() == 0 && intentFilter.countDataPaths() == 0) { - // Don't count generic handlers. - return false; - } - boolean isWildCardHost = false; - Iterator<IntentFilter.AuthorityEntry> it = intentFilter.authoritiesIterator(); - while (it != null && it.hasNext()) { - IntentFilter.AuthorityEntry entry = it.next(); - if ("*".equals(entry.getHost())) { - isWildCardHost = true; - break; - } - } - if (isWildCardHost) { - return false; - } - if (!TextUtils.isEmpty(filterPackageName) - && (info.activityInfo == null - || !info.activityInfo.packageName.equals(filterPackageName))) { - return false; - } - return true; - } - - public static ArrayList<String> getSpecializedHandlersWithFilter(List<ResolveInfo> infos, - String filterPackageName, boolean handlesInstantAppLaunchingInternally) { - ArrayList<String> result = new ArrayList<>(); - if (infos == null) { - return result; - } - - for (ResolveInfo info : infos) { - if (!matchResolveInfoExceptWildCardHost(info, filterPackageName)) { - continue; - } - - if (info.activityInfo != null) { - if (handlesInstantAppLaunchingInternally - && IntentUtils.isInstantAppResolveInfo(info)) { - // Don't add the Instant Apps launcher as a specialized handler if the embedder - // handles launching of Instant Apps itself. - continue; - } - - result.add(info.activityInfo.packageName); - } else { - result.add(""); - } - } - return result; - } - - /** - * @return Default SMS application's package name at the system level. Null if there isn't any. - */ - - @VisibleForTesting - protected String getDefaultSmsPackageNameFromSystem() { - return Telephony.Sms.getDefaultSmsPackage(ContextUtils.getApplicationContext()); - } - - /** - * @return The last committed URL from the WebContents. - */ - @VisibleForTesting - protected String getLastCommittedUrl() { - if (mDelegate.getWebContents() == null) return null; - return mDelegate.getWebContents().getLastCommittedUrl(); - } - - private void recordIntentActionMetrics(Intent intent) { - String action = intent.getAction(); - @StandardActions - int standardAction; - if (TextUtils.isEmpty(action)) { - standardAction = StandardActions.VIEW; - } else { - standardAction = getStandardAction(action); - } - RecordHistogram.recordEnumeratedHistogram( - INTENT_ACTION_HISTOGRAM, standardAction, StandardActions.NUM_ENTRIES); - } - - /** - * @param url The requested url. - * @return Whether we should block the navigation and request file access before proceeding. - */ - @VisibleForTesting - protected boolean shouldRequestFileAccess(String url) { - // If the tab is null, then do not attempt to prompt for access. - if (!mDelegate.hasValidTab()) return false; - - // If the url points inside of Chromium's data directory, no permissions are necessary. - // This is required to prevent permission prompt when uses wants to access offline pages. - if (url.startsWith(UrlConstants.FILE_URL_PREFIX + PathUtils.getDataDirectory())) { - return false; - } - - return !mDelegate.getWindowAndroid().hasPermission(permission.READ_EXTERNAL_STORAGE) - && mDelegate.getWindowAndroid().canRequestPermission( - permission.READ_EXTERNAL_STORAGE); - } - - @Nullable - private String getReferrerUrl() { - // TODO (thildebr): Investigate whether or not we can use getLastCommittedUrl() instead of - // the NavigationController. - if (!mDelegate.hasValidTab() || mDelegate.getWebContents() == null) return null; - - NavigationController nController = mDelegate.getWebContents().getNavigationController(); - int index = nController.getLastCommittedEntryIndex(); - if (index == -1) return null; - - NavigationEntry entry = nController.getEntryAtIndex(index); - if (entry == null) return null; - - return entry.getUrl(); - } - - /** - * @return whether this navigation is from the search results page. - */ - @VisibleForTesting - protected boolean isSerpReferrer() { - String referrerUrl = getReferrerUrl(); - if (referrerUrl == null) return false; - - return UrlUtilitiesJni.get().isGoogleSearchUrl(referrerUrl); - } - - private boolean isGoogleReferrer() { - String referrerUrl = getReferrerUrl(); - if (referrerUrl == null) return false; - - return UrlUtilitiesJni.get().isGoogleSubDomainUrl(referrerUrl); - } - - private @StandardActions int getStandardAction(String action) { - switch (action) { - case Intent.ACTION_MAIN: - return StandardActions.MAIN; - case Intent.ACTION_VIEW: - return StandardActions.VIEW; - case Intent.ACTION_ATTACH_DATA: - return StandardActions.ATTACH_DATA; - case Intent.ACTION_EDIT: - return StandardActions.EDIT; - case Intent.ACTION_PICK: - return StandardActions.PICK; - case Intent.ACTION_CHOOSER: - return StandardActions.CHOOSER; - case Intent.ACTION_GET_CONTENT: - return StandardActions.GET_CONTENT; - case Intent.ACTION_DIAL: - return StandardActions.DIAL; - case Intent.ACTION_CALL: - return StandardActions.CALL; - case Intent.ACTION_SEND: - return StandardActions.SEND; - case Intent.ACTION_SENDTO: - return StandardActions.SENDTO; - case Intent.ACTION_ANSWER: - return StandardActions.ANSWER; - case Intent.ACTION_INSERT: - return StandardActions.INSERT; - case Intent.ACTION_DELETE: - return StandardActions.DELETE; - case Intent.ACTION_RUN: - return StandardActions.RUN; - case Intent.ACTION_SYNC: - return StandardActions.SYNC; - case Intent.ACTION_PICK_ACTIVITY: - return StandardActions.PICK_ACTIVITY; - case Intent.ACTION_SEARCH: - return StandardActions.SEARCH; - case Intent.ACTION_WEB_SEARCH: - return StandardActions.WEB_SEARCH; - case Intent.ACTION_FACTORY_TEST: - return StandardActions.FACTORY_TEST; - default: - return StandardActions.OTHER; - } - } -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java deleted file mode 100644 index fe172dadec4..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationParams.java +++ /dev/null @@ -1,330 +0,0 @@ -// 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.components.external_intents; - -import androidx.annotation.Nullable; - -import org.chromium.url.Origin; - -/** - * A container object for passing navigation parameters to {@link ExternalNavigationHandler}. - */ -public class ExternalNavigationParams { - /** The URL which we are navigating to. */ - private final String mUrl; - - /** Whether we are currently in an incognito context. */ - private final boolean mIsIncognito; - - /** The referrer URL for the current navigation. */ - private final String mReferrerUrl; - - /** The page transition type for the current navigation. */ - private final int mPageTransition; - - /** Whether the current navigation is a redirect. */ - private final boolean mIsRedirect; - - /** Whether Chrome has to be in foreground for external navigation to occur. */ - private final boolean mApplicationMustBeInForeground; - - /** A redirect handler. */ - private final RedirectHandler mRedirectHandler; - - /** Whether the intent should force a new tab to open. */ - private final boolean mOpenInNewTab; - - /** Whether this navigation happens in background tab. */ - private final boolean mIsBackgroundTabNavigation; - - /** Whether this navigation happens in main frame. */ - private final boolean mIsMainFrame; - - /** - * The package name of the TWA or WebAPK within which the navigation is happening. - * Null if the navigation is not within one of these wrapping APKs. - */ - private final String mNativeClientPackageName; - - /** Whether this navigation is launched by user gesture. */ - private final boolean mHasUserGesture; - - /** - * Whether the current tab should be closed when an URL load was overridden and an - * intent launched. - */ - private final boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; - - /** - * Whether the navigation is initiated by the renderer. - */ - private boolean mIsRendererInitiated; - - /** - * The origin that initiates the navigation, could be null. - */ - private Origin mInitiatorOrigin; - - private ExternalNavigationParams(String url, boolean isIncognito, String referrerUrl, - int pageTransition, boolean isRedirect, boolean appMustBeInForeground, - RedirectHandler redirectHandler, boolean openInNewTab, - boolean isBackgroundTabNavigation, boolean isMainFrame, String nativeClientPackageName, - boolean hasUserGesture, boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, - boolean isRendererInitiated, @Nullable Origin initiatorOrigin) { - mUrl = url; - mIsIncognito = isIncognito; - mPageTransition = pageTransition; - mReferrerUrl = referrerUrl; - mIsRedirect = isRedirect; - mApplicationMustBeInForeground = appMustBeInForeground; - mRedirectHandler = redirectHandler; - mOpenInNewTab = openInNewTab; - mIsBackgroundTabNavigation = isBackgroundTabNavigation; - mIsMainFrame = isMainFrame; - mNativeClientPackageName = nativeClientPackageName; - mHasUserGesture = hasUserGesture; - mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent = - shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; - mIsRendererInitiated = isRendererInitiated; - mInitiatorOrigin = initiatorOrigin; - } - - /** @return The URL to potentially open externally. */ - public String getUrl() { - return mUrl; - } - - /** @return Whether we are currently in incognito mode. */ - public boolean isIncognito() { - return mIsIncognito; - } - - /** @return The referrer URL. */ - public String getReferrerUrl() { - return mReferrerUrl; - } - - /** @return The page transition for the current navigation. */ - public int getPageTransition() { - return mPageTransition; - } - - /** @return Whether the navigation is part of a redirect. */ - public boolean isRedirect() { - return mIsRedirect; - } - - /** @return Whether the application has to be in foreground to open the URL. */ - public boolean isApplicationMustBeInForeground() { - return mApplicationMustBeInForeground; - } - - /** @return The redirect handler. */ - public RedirectHandler getRedirectHandler() { - return mRedirectHandler; - } - - /** - * @return Whether the external navigation should be opened in a new tab if handled by Chrome - * through the intent picker. - */ - public boolean isOpenInNewTab() { - return mOpenInNewTab; - } - - /** @return Whether this navigation happens in background tab. */ - public boolean isBackgroundTabNavigation() { - return mIsBackgroundTabNavigation; - } - - /** @return Whether this navigation happens in main frame. */ - public boolean isMainFrame() { - return mIsMainFrame; - } - - /** - * @return The package name of the TWA or WebAPK within which the navigation is happening. - * Null if the navigation is not within one of these wrapping APKs. - */ - public String nativeClientPackageName() { - return mNativeClientPackageName; - } - - /** @return Whether this navigation is launched by user gesture. */ - public boolean hasUserGesture() { - return mHasUserGesture; - } - - /** - * @return Whether the current tab should be closed when an URL load was overridden and an - * intent launched. - */ - public boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent() { - return mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; - } - - /** - * @return Whether the navigation is initiated by renderer. - */ - public boolean isRendererInitiated() { - return mIsRendererInitiated; - } - - /** - * @return The origin that initiates the navigation. - */ - @Nullable - public Origin getInitiatorOrigin() { - return mInitiatorOrigin; - } - - /** The builder for {@link ExternalNavigationParams} objects. */ - public static class Builder { - /** The URL which we are navigating to. */ - private String mUrl; - - /** Whether we are currently in an incognito context. */ - private boolean mIsIncognito; - - /** The referrer URL for the current navigation. */ - private String mReferrerUrl; - - /** The page transition type for the current navigation. */ - private int mPageTransition; - - /** Whether the current navigation is a redirect. */ - private boolean mIsRedirect; - - /** Whether Chrome has to be in foreground for external navigation to occur. */ - private boolean mApplicationMustBeInForeground; - - /** A redirect handler. */ - private RedirectHandler mRedirectHandler; - - /** Whether the intent should force a new tab to open. */ - private boolean mOpenInNewTab; - - /** Whether this navigation happens in background tab. */ - private boolean mIsBackgroundTabNavigation; - - /** Whether this navigation happens in main frame. */ - private boolean mIsMainFrame; - - /** - * The package name of the TWA or WebAPK within which the navigation is happening. - * Null if the navigation is not within one of these wrapping APKs. - */ - private String mNativeClientPackageName; - - /** Whether this navigation is launched by user gesture. */ - private boolean mHasUserGesture; - - /** - * Whether the current tab should be closed when an URL load was overridden and an - * intent launched. - */ - private boolean mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent; - - /** - * Whether the navigation is initiated by the renderer. - */ - private boolean mIsRendererInitiated; - - /** - * The origin that initiates the navigation, could be null. - */ - private Origin mInitiatorOrigin; - - public Builder(String url, boolean isIncognito) { - mUrl = url; - mIsIncognito = isIncognito; - } - - public Builder(String url, boolean isIncognito, String referrer, int pageTransition, - boolean isRedirect) { - mUrl = url; - mIsIncognito = isIncognito; - mReferrerUrl = referrer; - mPageTransition = pageTransition; - mIsRedirect = isRedirect; - } - - /** Specify whether the application must be in foreground to launch an external intent. */ - public Builder setApplicationMustBeInForeground(boolean v) { - mApplicationMustBeInForeground = v; - return this; - } - - /** Sets a tab redirect handler. */ - public Builder setRedirectHandler(RedirectHandler handler) { - mRedirectHandler = handler; - return this; - } - - /** Sets whether we want to open the intent URL in new tab, if handled by Chrome. */ - public Builder setOpenInNewTab(boolean v) { - mOpenInNewTab = v; - return this; - } - - /** Sets whether this navigation happens in background tab. */ - public Builder setIsBackgroundTabNavigation(boolean v) { - mIsBackgroundTabNavigation = v; - return this; - } - - /** Sets whether this navigation happens in main frame. */ - public Builder setIsMainFrame(boolean v) { - mIsMainFrame = v; - return this; - } - - /** Sets the package name of the TWA or WebAPK within which the navigation is happening. **/ - public Builder setNativeClientPackageName(String v) { - mNativeClientPackageName = v; - return this; - } - - /** Sets whether this navigation happens in main frame. */ - public Builder setHasUserGesture(boolean v) { - mHasUserGesture = v; - return this; - } - - /** - * Sets whether the current tab should be closed when an URL load was overridden and an - * intent launched. - */ - public Builder setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(boolean v) { - mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent = v; - return this; - } - - /** - * Sets whether the navigation is initiated by renderer. - */ - public Builder setIsRendererInitiated(boolean v) { - mIsRendererInitiated = v; - return this; - } - - /** - * Sets the origin that initiates the navigation. - */ - public Builder setInitiatorOrigin(@Nullable Origin v) { - mInitiatorOrigin = v; - return this; - } - - /** @return A fully constructed {@link ExternalNavigationParams} object. */ - public ExternalNavigationParams build() { - return new ExternalNavigationParams(mUrl, mIsIncognito, mReferrerUrl, mPageTransition, - mIsRedirect, mApplicationMustBeInForeground, mRedirectHandler, mOpenInNewTab, - mIsBackgroundTabNavigation, mIsMainFrame, mNativeClientPackageName, - mHasUserGesture, mShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent, - mIsRendererInitiated, mInitiatorOrigin); - } - } -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java deleted file mode 100644 index 3e69919e88e..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateClient.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 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.components.external_intents; - -import android.app.Activity; - -import org.chromium.components.navigation_interception.NavigationParams; -import org.chromium.content_public.browser.WebContents; - -/** - * An interface via which the embedder provides the context information that - * InterceptNavigationDelegateImpl needs. - */ -public interface InterceptNavigationDelegateClient { - /* Returns the WebContents in the context of which this InterceptNavigationDelegateImpl instance - * is operating. */ - WebContents getWebContents(); - - /* Creates an ExternalNavigationHandler instance that is configured for this client. */ - ExternalNavigationHandler createExternalNavigationHandler(); - - /* Returns the time of the user's last interaction with the app. */ - long getLastUserInteractionTime(); - - /* Gets a RedirectHandler instance that is associated with this client, creating it if - * necessary. */ - RedirectHandler getOrCreateRedirectHandler(); - - /* Creates an AuthenticatorNavigationInterceptor instance that is configured for this client. - */ - AuthenticatorNavigationInterceptor createAuthenticatorNavigationInterceptor(); - - /* Returns whether whether the tab associated with this client is incognito. */ - boolean isIncognito(); - - /* Returns whether whether the tab associated with this client is currently hidden. */ - boolean isHidden(); - - /* Returns the Activity associated with this client. */ - Activity getActivity(); - - /* Returns true if the tab associated with this client was launched from an external app. */ - boolean wasTabLaunchedFromExternalApp(); - - /* Returns true if the tab associated with this client was launched from a long press in the - * background. */ - boolean wasTabLaunchedFromLongPressInBackground(); - - /* Invoked when the tab associated with this client should be closed. */ - void closeTab(); - - /* Invoked when a navigation has begun in the InterceptNavigationDelegateImpl instance - * associated with this instance. */ - void onNavigationStarted(NavigationParams params); -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java deleted file mode 100644 index 686721169fa..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/InterceptNavigationDelegateImpl.java +++ /dev/null @@ -1,296 +0,0 @@ -// 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.components.external_intents; - -import androidx.annotation.VisibleForTesting; - -import org.chromium.base.ContextUtils; -import org.chromium.base.annotations.JNINamespace; -import org.chromium.base.annotations.NativeMethods; -import org.chromium.base.task.PostTask; -import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult; -import org.chromium.components.navigation_interception.InterceptNavigationDelegate; -import org.chromium.components.navigation_interception.NavigationParams; -import org.chromium.content_public.browser.NavigationController; -import org.chromium.content_public.browser.NavigationHandle; -import org.chromium.content_public.browser.UiThreadTaskTraits; -import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.common.ConsoleMessageLevel; - -/** - * Class that controls navigations and allows to intercept them. It is used on Android to 'convert' - * certain navigations to Intents to 3rd party applications. - * Note the Intent is often created together with a new empty tab which then should be closed - * immediately. Closing the tab will cancel the navigation that this delegate is running for, - * hence can cause UAF error. It should be done in an asynchronous fashion to avoid it. - * See https://crbug.com/732260. - */ -@JNINamespace("external_intents") -public class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate { - private final AuthenticatorNavigationInterceptor mAuthenticatorHelper; - private InterceptNavigationDelegateClient mClient; - private @OverrideUrlLoadingResult int mLastOverrideUrlLoadingResult = - OverrideUrlLoadingResult.NO_OVERRIDE; - private WebContents mWebContents; - private ExternalNavigationHandler mExternalNavHandler; - - /** - * Whether forward history should be cleared after navigation is committed. - */ - private boolean mClearAllForwardHistoryRequired; - private boolean mShouldClearRedirectHistoryForTabClobbering; - - /** - * Default constructor of {@link InterceptNavigationDelegateImpl}. - */ - public InterceptNavigationDelegateImpl(InterceptNavigationDelegateClient client) { - mClient = client; - mAuthenticatorHelper = mClient.createAuthenticatorNavigationInterceptor(); - associateWithWebContents(mClient.getWebContents()); - } - - // Invoked by the client when a navigation has finished in the context in which this object is - // operating. - public void onNavigationFinished(NavigationHandle navigation) { - if (!navigation.hasCommitted() || !navigation.isInMainFrame()) return; - maybeUpdateNavigationHistory(); - } - - public void setExternalNavigationHandler(ExternalNavigationHandler handler) { - mExternalNavHandler = handler; - } - - public void associateWithWebContents(WebContents webContents) { - if (mWebContents == webContents) return; - mWebContents = webContents; - if (mWebContents == null) return; - - // Lazily initialize the external navigation handler. - if (mExternalNavHandler == null) { - setExternalNavigationHandler(mClient.createExternalNavigationHandler()); - } - InterceptNavigationDelegateImplJni.get().associateWithWebContents(this, mWebContents); - } - - public boolean shouldIgnoreNewTab(String url, boolean incognito) { - if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthenticatorUrl(url)) { - return true; - } - - ExternalNavigationParams params = - new ExternalNavigationParams.Builder(url, incognito).setOpenInNewTab(true).build(); - mLastOverrideUrlLoadingResult = mExternalNavHandler.shouldOverrideUrlLoading(params); - return mLastOverrideUrlLoadingResult - != ExternalNavigationHandler.OverrideUrlLoadingResult.NO_OVERRIDE; - } - - @VisibleForTesting - public @OverrideUrlLoadingResult int getLastOverrideUrlLoadingResultForTests() { - return mLastOverrideUrlLoadingResult; - } - - @Override - public boolean shouldIgnoreNavigation(NavigationParams navigationParams) { - mClient.onNavigationStarted(navigationParams); - - String url = navigationParams.url; - long lastUserInteractionTime = mClient.getLastUserInteractionTime(); - - if (mAuthenticatorHelper != null && mAuthenticatorHelper.handleAuthenticatorUrl(url)) { - return true; - } - - RedirectHandler redirectHandler = null; - if (navigationParams.isMainFrame) { - redirectHandler = mClient.getOrCreateRedirectHandler(); - } else if (navigationParams.isExternalProtocol) { - // Only external protocol navigations are intercepted for iframe navigations. Since - // we do not see all previous navigations for the iframe, we can not build a complete - // redirect handler for each iframe. Nor can we use the top level redirect handler as - // that has the potential to incorrectly give access to the navigation due to previous - // main frame gestures. - // - // By creating a new redirect handler for each external navigation, we are specifically - // not covering the case where a gesture is carried over via a redirect. This is - // currently not feasible because we do not see all navigations for iframes and it is - // better to error on the side of caution and require direct user gestures for iframes. - redirectHandler = RedirectHandler.create(); - } else { - assert false; - return false; - } - redirectHandler.updateNewUrlLoading(navigationParams.pageTransitionType, - navigationParams.isRedirect, - navigationParams.hasUserGesture || navigationParams.hasUserGestureCarryover, - lastUserInteractionTime, getLastCommittedEntryIndex()); - - boolean shouldCloseTab = shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent(); - ExternalNavigationParams params = - buildExternalNavigationParams(navigationParams, redirectHandler, shouldCloseTab) - .build(); - @OverrideUrlLoadingResult - int result = mExternalNavHandler.shouldOverrideUrlLoading(params); - mLastOverrideUrlLoadingResult = result; - - switch (result) { - case OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT: - assert mExternalNavHandler.canExternalAppHandleUrl(url); - if (navigationParams.isMainFrame) { - onOverrideUrlLoadingAndLaunchIntent(shouldCloseTab); - } - return true; - case OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB: - mShouldClearRedirectHistoryForTabClobbering = true; - return true; - case OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION: - if (!shouldCloseTab && navigationParams.isMainFrame) { - onOverrideUrlLoadingAndLaunchIntent(shouldCloseTab); - } - return true; - case OverrideUrlLoadingResult.NO_OVERRIDE: - default: - if (navigationParams.isExternalProtocol) { - logBlockedNavigationToDevToolsConsole(url); - return true; - } - return false; - } - } - - /** - * Returns ExternalNavigationParams.Builder to generate ExternalNavigationParams for - * ExternalNavigationHandler#shouldOverrideUrlLoading(). - */ - public ExternalNavigationParams.Builder buildExternalNavigationParams( - NavigationParams navigationParams, RedirectHandler redirectHandler, - boolean shouldCloseTab) { - boolean isInitialTabLaunchInBackground = - mClient.wasTabLaunchedFromLongPressInBackground() && shouldCloseTab; - // http://crbug.com/448977: If a new tab is closed by this overriding, we should open an - // Intent in a new tab when Chrome receives it again. - return new ExternalNavigationParams - .Builder(navigationParams.url, mClient.isIncognito(), navigationParams.referrer, - navigationParams.pageTransitionType, navigationParams.isRedirect) - .setApplicationMustBeInForeground(true) - .setRedirectHandler(redirectHandler) - .setOpenInNewTab(shouldCloseTab) - .setIsBackgroundTabNavigation(mClient.isHidden() && !isInitialTabLaunchInBackground) - .setIsMainFrame(navigationParams.isMainFrame) - .setHasUserGesture(navigationParams.hasUserGesture) - .setShouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent( - shouldCloseTab && navigationParams.isMainFrame) - .setIsRendererInitiated(navigationParams.isRendererInitiated) - .setInitiatorOrigin(navigationParams.initiatorOrigin); - } - - /** - * Updates navigation history if navigation is canceled due to intent handler. We go back to the - * last committed entry index which was saved before the navigation, and remove the empty - * entries from the navigation history. See crbug.com/426679 - */ - public void maybeUpdateNavigationHistory() { - WebContents webContents = mClient.getWebContents(); - if (mClearAllForwardHistoryRequired && webContents != null) { - webContents.getNavigationController().pruneForwardEntries(); - } else if (mShouldClearRedirectHistoryForTabClobbering && webContents != null) { - // http://crbug/479056: Even if we clobber the current tab, we want to remove - // redirect history to be consistent. - NavigationController navigationController = webContents.getNavigationController(); - int indexBeforeRedirection = - mClient.getOrCreateRedirectHandler() - .getLastCommittedEntryIndexBeforeStartingNavigation(); - int lastCommittedEntryIndex = getLastCommittedEntryIndex(); - for (int i = lastCommittedEntryIndex - 1; i > indexBeforeRedirection; --i) { - boolean ret = navigationController.removeEntryAtIndex(i); - assert ret; - } - } - mClearAllForwardHistoryRequired = false; - mShouldClearRedirectHistoryForTabClobbering = false; - } - - @VisibleForTesting - public AuthenticatorNavigationInterceptor getAuthenticatorNavigationInterceptor() { - return mAuthenticatorHelper; - } - - private int getLastCommittedEntryIndex() { - if (mClient.getWebContents() == null) return -1; - return mClient.getWebContents().getNavigationController().getLastCommittedEntryIndex(); - } - - private boolean shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent() { - if (mClient.getWebContents() == null) return false; - if (!mClient.getWebContents().getNavigationController().canGoToOffset(0)) return true; - - // http://crbug/415948 : if the last committed entry index which was saved before this - // navigation is invalid, it means that this navigation is the first one since this tab was - // created. - // In such case, we would like to close this tab. - if (mClient.getOrCreateRedirectHandler().isOnNavigation()) { - return mClient.getOrCreateRedirectHandler() - .getLastCommittedEntryIndexBeforeStartingNavigation() - == RedirectHandler.INVALID_ENTRY_INDEX; - } - return false; - } - - /** - * Called when Chrome decides to override URL loading and launch an intent or an asynchronous - * action. - * @param shouldCloseTab - */ - private void onOverrideUrlLoadingAndLaunchIntent(boolean shouldCloseTab) { - if (mClient.getWebContents() == null) return; - - // Before leaving Chrome, close the empty child tab. - // If a new tab is created through JavaScript open to load this - // url, we would like to close it as we will load this url in a - // different Activity. - if (shouldCloseTab) { - if (mClient.wasTabLaunchedFromExternalApp()) { - // Moving task back before closing the tab allows back button to function better - // when Chrome was an intermediate link redirector between two apps. - // crbug.com/487938. - mClient.getActivity().moveTaskToBack(false); - } - // Defer closing a tab (and the associated WebContents) till the navigation - // request and the throttle finishes the job with it. - PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { - @Override - public void run() { - mClient.closeTab(); - } - }); - } else if (mClient.getOrCreateRedirectHandler().isOnNavigation()) { - int lastCommittedEntryIndexBeforeNavigation = - mClient.getOrCreateRedirectHandler() - .getLastCommittedEntryIndexBeforeStartingNavigation(); - if (getLastCommittedEntryIndex() > lastCommittedEntryIndexBeforeNavigation) { - // http://crbug/426679 : we want to go back to the last committed entry index which - // was saved before this navigation, and remove the empty entries from the - // navigation history. - mClearAllForwardHistoryRequired = true; - mClient.getWebContents().getNavigationController().goToNavigationIndex( - lastCommittedEntryIndexBeforeNavigation); - } - } - } - - private void logBlockedNavigationToDevToolsConsole(String url) { - int resId = mExternalNavHandler.canExternalAppHandleUrl(url) - ? R.string.blocked_navigation_warning - : R.string.unreachable_navigation_warning; - mClient.getWebContents().addMessageToDevToolsConsole(ConsoleMessageLevel.WARNING, - ContextUtils.getApplicationContext().getString(resId, url)); - } - - @NativeMethods - interface Natives { - void associateWithWebContents( - InterceptNavigationDelegateImpl nativeInterceptNavigationDelegateImpl, - WebContents webContents); - } -} diff --git a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandler.java b/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandler.java deleted file mode 100644 index 3bdfec4796e..00000000000 --- a/chromium/components/external_intents/android/java/src/org/chromium/components/external_intents/RedirectHandler.java +++ /dev/null @@ -1,301 +0,0 @@ -// 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.components.external_intents; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.os.SystemClock; -import android.provider.Browser; -import android.text.TextUtils; - -import org.chromium.base.ContextUtils; -import org.chromium.base.IntentUtils; -import org.chromium.base.PackageManagerUtils; -import org.chromium.ui.base.PageTransition; - -import java.util.HashSet; -import java.util.List; - -/** - * This class contains the logic to determine effective navigation/redirect. - */ -public class RedirectHandler { - /** - * An invalid entry index. - */ - public static final int INVALID_ENTRY_INDEX = -1; - public static final long INVALID_TIME = -1; - - private static final int NAVIGATION_TYPE_NONE = 0; - private static final int NAVIGATION_TYPE_FROM_INTENT = 1; - private static final int NAVIGATION_TYPE_FROM_USER_TYPING = 2; - private static final int NAVIGATION_TYPE_FROM_LINK_WITHOUT_USER_GESTURE = 3; - private static final int NAVIGATION_TYPE_FROM_RELOAD = 4; - private static final int NAVIGATION_TYPE_OTHER = 5; - - private Intent mInitialIntent; - // A resolver list which includes all resolvers of |mInitialIntent|. - private final HashSet<ComponentName> mCachedResolvers = new HashSet<ComponentName>(); - private boolean mIsInitialIntentHeadingToChrome; - private boolean mIsCustomTabIntent; - - private long mLastNewUrlLoadingTime = INVALID_TIME; - private boolean mIsOnEffectiveRedirectChain; - private int mInitialNavigationType; - private int mLastCommittedEntryIndexBeforeStartingNavigation; - - private boolean mShouldNotOverrideUrlLoadingOnCurrentRedirectChain; - - public static RedirectHandler create() { - return new RedirectHandler(); - } - - protected RedirectHandler() {} - - /** - * Updates |mIntentHistory| and |mLastIntentUpdatedTime|. If |intent| comes from chrome and - * currently |mIsOnEffectiveIntentRedirectChain| is true, that means |intent| was sent from - * this tab because only the front tab or a new tab can receive an intent from chrome. In that - * case, |intent| is added to |mIntentHistory|. - * Otherwise, |mIntentHistory| and |mPreviousResolvers| are cleared, and then |intent| is put - * into |mIntentHistory|. - */ - public void updateIntent(Intent intent, boolean isCustomTabIntent, boolean sendToExternalApps, - boolean isCCTExternalLinkHandlingEnabled) { - clear(); - - if (intent == null || !Intent.ACTION_VIEW.equals(intent.getAction())) { - return; - } - - mIsCustomTabIntent = isCustomTabIntent; - boolean checkIsToChrome = true; - // All custom tabs VIEW intents are by design explicit intents, so the presence of package - // name doesn't imply they have to be handled by Chrome explicitly. Check if external apps - // should be checked for handling the initial redirect chain. - if (mIsCustomTabIntent) { - checkIsToChrome = !(sendToExternalApps && isCCTExternalLinkHandlingEnabled); - } - - if (checkIsToChrome) mIsInitialIntentHeadingToChrome = isIntentToChrome(intent); - - // A copy of the intent with component cleared to find resolvers. - mInitialIntent = new Intent(intent).setComponent(null); - Intent selector = mInitialIntent.getSelector(); - if (selector != null) selector.setComponent(null); - } - - private static boolean isIntentToChrome(Intent intent) { - String chromePackageName = ContextUtils.getApplicationContext().getPackageName(); - return TextUtils.equals(chromePackageName, intent.getPackage()) - || TextUtils.equals(chromePackageName, - IntentUtils.safeGetStringExtra(intent, Browser.EXTRA_APPLICATION_ID)); - } - - private void clearIntentHistory() { - mIsInitialIntentHeadingToChrome = false; - mIsCustomTabIntent = false; - mInitialIntent = null; - mCachedResolvers.clear(); - } - - /** - * Resets all variables except timestamps. - */ - public void clear() { - clearIntentHistory(); - mInitialNavigationType = NAVIGATION_TYPE_NONE; - mIsOnEffectiveRedirectChain = false; - mLastCommittedEntryIndexBeforeStartingNavigation = 0; - mShouldNotOverrideUrlLoadingOnCurrentRedirectChain = false; - } - - /** - * Will cause shouldNotOverrideUrlLoading() to return true until a new user-initiated navigation - * occurs. - */ - public void setShouldNotOverrideUrlLoadingOnCurrentRedirectChain() { - mShouldNotOverrideUrlLoadingOnCurrentRedirectChain = true; - } - - /** - * Updates new url loading information to trace navigation. - * A time based heuristic is used to determine if this loading is an effective redirect or not - * if core of |pageTransType| is LINK. - * - * http://crbug.com/322567 : Trace navigation started from an external app. - * http://crbug.com/331571 : Trace navigation started from user typing to do not override such - * navigation. - * http://crbug.com/426679 : Trace every navigation and the last committed entry index right - * before starting the navigation. - * - * @param pageTransType page transition type of this loading. - * @param isRedirect whether this loading is http redirect or not. - * @param hasUserGesture whether this loading is started by a user gesture. - * @param lastUserInteractionTime time when the last user interaction was made. - * @param lastCommittedEntryIndex the last committed entry index right before this loading. - */ - public void updateNewUrlLoading(int pageTransType, boolean isRedirect, boolean hasUserGesture, - long lastUserInteractionTime, int lastCommittedEntryIndex) { - long prevNewUrlLoadingTime = mLastNewUrlLoadingTime; - mLastNewUrlLoadingTime = SystemClock.elapsedRealtime(); - - int pageTransitionCore = pageTransType & PageTransition.CORE_MASK; - - boolean isNewLoadingStartedByUser = false; - boolean isFromIntent = pageTransitionCore == PageTransition.LINK - && (pageTransType & PageTransition.FROM_API) != 0; - if (!isRedirect) { - if ((pageTransType & PageTransition.FORWARD_BACK) != 0) { - isNewLoadingStartedByUser = true; - } else if (pageTransitionCore != PageTransition.LINK - && pageTransitionCore != PageTransition.FORM_SUBMIT) { - isNewLoadingStartedByUser = true; - } else if (prevNewUrlLoadingTime == INVALID_TIME || isFromIntent - || lastUserInteractionTime > prevNewUrlLoadingTime) { - isNewLoadingStartedByUser = true; - } - } - - if (isNewLoadingStartedByUser) { - // Updates mInitialNavigationType for a new loading started by a user's gesture. - if (isFromIntent && mInitialIntent != null) { - mInitialNavigationType = NAVIGATION_TYPE_FROM_INTENT; - } else { - clearIntentHistory(); - if (pageTransitionCore == PageTransition.TYPED) { - mInitialNavigationType = NAVIGATION_TYPE_FROM_USER_TYPING; - } else if (pageTransitionCore == PageTransition.RELOAD - || (pageTransType & PageTransition.FORWARD_BACK) != 0) { - mInitialNavigationType = NAVIGATION_TYPE_FROM_RELOAD; - } else if (pageTransitionCore == PageTransition.LINK && !hasUserGesture) { - mInitialNavigationType = NAVIGATION_TYPE_FROM_LINK_WITHOUT_USER_GESTURE; - } else { - mInitialNavigationType = NAVIGATION_TYPE_OTHER; - } - } - mIsOnEffectiveRedirectChain = false; - mLastCommittedEntryIndexBeforeStartingNavigation = lastCommittedEntryIndex; - mShouldNotOverrideUrlLoadingOnCurrentRedirectChain = false; - } else if (mInitialNavigationType != NAVIGATION_TYPE_NONE) { - // Redirect chain starts from the second url loading. - mIsOnEffectiveRedirectChain = true; - } - } - - /** - * @return whether on effective intent redirect chain or not. - */ - public boolean isOnEffectiveIntentRedirectChain() { - return mInitialNavigationType == NAVIGATION_TYPE_FROM_INTENT && mIsOnEffectiveRedirectChain; - } - - /** - * @param hasExternalProtocol whether the destination URI has an external protocol or not. - * @return whether we should stay in Chrome or not. - */ - public boolean shouldStayInApp(boolean hasExternalProtocol) { - return shouldStayInApp(hasExternalProtocol, false); - } - - /** - * @param hasExternalProtocol whether the destination URI has an external protocol or not. - * @param isForTrustedCallingApp whether the app we would launch to is trusted and what launched - * Chrome. - * @return whether we should stay in Chrome or not. - */ - public boolean shouldStayInApp(boolean hasExternalProtocol, boolean isForTrustedCallingApp) { - // http://crbug/424029 : Need to stay in Chrome for an intent heading explicitly to Chrome. - // http://crbug/881740 : Relax stay in Chrome restriction for Custom Tabs. - return (mIsInitialIntentHeadingToChrome && !hasExternalProtocol) - || shouldNavigationTypeStayInApp(isForTrustedCallingApp); - } - - /** - * @return Whether the current navigation is of the type that should always stay in Chrome. - */ - public boolean shouldNavigationTypeStayInApp() { - return shouldNavigationTypeStayInApp(false); - } - - private boolean shouldNavigationTypeStayInApp(boolean isForTrustedCallingApp) { - // http://crbug.com/162106: Never leave Chrome from a refresh. - if (mInitialNavigationType == NAVIGATION_TYPE_FROM_RELOAD) return true; - - // If the app we would navigate to is trusted and what launched Chrome, allow the - // navigation. - if (isForTrustedCallingApp) return false; - - // Otherwise allow navigation out of the app only with a user gesture. - return mInitialNavigationType == NAVIGATION_TYPE_FROM_LINK_WITHOUT_USER_GESTURE; - } - - /** - * @return Whether this navigation is initiated by a Custom Tabs {@link Intent}. - */ - public boolean isFromCustomTabIntent() { - return mIsCustomTabIntent; - } - - /** - * @return whether navigation is from a user's typing or not. - */ - public boolean isNavigationFromUserTyping() { - return mInitialNavigationType == NAVIGATION_TYPE_FROM_USER_TYPING; - } - - /** - * @return whether we should stay in Chrome or not. - */ - public boolean shouldNotOverrideUrlLoading() { - return mShouldNotOverrideUrlLoadingOnCurrentRedirectChain; - } - - /** - * @return whether on navigation or not. - */ - public boolean isOnNavigation() { - return mInitialNavigationType != NAVIGATION_TYPE_NONE; - } - - /** - * @return the last committed entry index which was saved before starting this navigation. - */ - public int getLastCommittedEntryIndexBeforeStartingNavigation() { - return mLastCommittedEntryIndexBeforeStartingNavigation; - } - - /** - * @return whether |intent| has a new resolver against |mIntentHistory| or not. - */ - public boolean hasNewResolver(List<ResolveInfo> resolvingInfos) { - if (mInitialIntent == null) { - return !resolvingInfos.isEmpty(); - } - - if (mCachedResolvers.isEmpty()) { - for (ResolveInfo r : PackageManagerUtils.queryIntentActivities(mInitialIntent, 0)) { - mCachedResolvers.add( - new ComponentName(r.activityInfo.packageName, r.activityInfo.name)); - } - } - if (resolvingInfos.size() > mCachedResolvers.size()) return true; - for (ResolveInfo r : resolvingInfos) { - if (!mCachedResolvers.contains( - new ComponentName(r.activityInfo.packageName, r.activityInfo.name))) { - return true; - } - } - return false; - } - - /** - * @return The initial intent of a redirect chain, if available. - */ - public Intent getInitialIntent() { - return mInitialIntent; - } -} |