// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.external_intents;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.content.Context;
import android.content.ContextWrapper;
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.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Browser;
import android.test.mock.MockPackageManager;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Features;
import org.chromium.base.test.util.MaxAndroidSdkLevel;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.components.external_intents.ExternalNavigationHandler.IncognitoDialogDelegate;
import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
import org.chromium.components.external_intents.ExternalNavigationParams.AsyncActionTakenParams;
import org.chromium.components.external_intents.ExternalNavigationParams.AsyncActionTakenParams.AsyncActionTakenType;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.test.util.BlankUiTestActivity;
import org.chromium.ui.test.util.modaldialog.FakeModalDialogManager;
import org.chromium.url.GURL;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
/** Instrumentation tests for {@link ExternalNavigationHandler}. */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
@Features.DisableFeatures(ExternalIntentsFeatures.EXTERNAL_NAVIGATION_DEBUG_LOGS_NAME)
@Features.EnableFeatures({
ExternalIntentsFeatures.BLOCK_FRAME_RENAVIGATIONS_NAME,
ExternalIntentsFeatures.BLOCK_INTENTS_TO_SELF_NAME
})
public class ExternalNavigationHandlerTest {
// Expectations
private static final int IGNORE = 0x0;
private static final int START_INCOGNITO = 0x1;
private static final int START_WEBAPK = 0x2;
private static final int START_FILE = 0x4;
private static final int START_OTHER_ACTIVITY = 0x10;
private static final int INTENT_SANITIZATION_EXCEPTION = 0x20;
private static final boolean IS_CUSTOM_TAB_INTENT = true;
private static final boolean SEND_TO_EXTERNAL_APPS = true;
private static final boolean INTENT_STARTED_TASK = true;
private static final String SELF_PACKAGE_NAME = "test.app.name";
private static final String INTENT_APP_PACKAGE_NAME = "com.imdb.mobile";
private static final String YOUTUBE_URL = "http://youtube.com/";
private static final String YOUTUBE_MOBILE_URL = "http://m.youtube.com";
private static final String YOUTUBE_PACKAGE_NAME = "youtube";
private static final String OTHER_BROWSER_PACKAGE = "com.other.browser";
private static final String SEARCH_RESULT_URL_FOR_TOM_HANKS =
"https://www.google.com/search?q=tom+hanks";
private static final String IMDB_WEBPAGE_FOR_TOM_HANKS = "http://m.imdb.com/name/nm0000158";
private static final String INTENT_URL_WITH_FALLBACK_URL =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(IMDB_WEBPAGE_FOR_TOM_HANKS)
+ ";end";
private static final String INTENT_URL_WITH_FALLBACK_URL_WITHOUT_PACKAGE_NAME =
"intent:///name/nm0000158#Intent;scheme=imdb;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(IMDB_WEBPAGE_FOR_TOM_HANKS)
+ ";end";
private static final String SOME_JAVASCRIPT_PAGE = "javascript:window.open(0);";
private static final String INTENT_URL_WITH_JAVASCRIPT_FALLBACK_URL =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(SOME_JAVASCRIPT_PAGE)
+ ";end";
private static final String IMDB_APP_INTENT_FOR_TOM_HANKS = "imdb:///name/nm0000158";
private static final String INTENT_URL_WITH_CHAIN_FALLBACK_URL =
"intent://scan/#Intent;scheme=zxing;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode("http://url.myredirector.com/aaa")
+ ";end";
private static final String ENCODED_MARKET_REFERRER =
"_placement%3D{placement}%26network%3D{network}%26device%3D{devicemodel}";
private static final String INTENT_APP_NOT_INSTALLED_DEFAULT_MARKET_REFERRER =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;end";
private static final String INTENT_APP_NOT_INSTALLED_WITH_MARKET_REFERRER =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;S."
+ ExternalNavigationHandler.EXTRA_MARKET_REFERRER
+ "="
+ ENCODED_MARKET_REFERRER
+ ";end";
private static final String INTENT_URL_FOR_SELF_CUSTOM_TABS =
"intent://example.com#Intent;"
+ "package="
+ SELF_PACKAGE_NAME
+ ";"
+ "action=android.intent.action.VIEW;"
+ "scheme=http;"
+ "S.android.support.customtabs.extra.SESSION=;"
+ "end;";
private static final String INTENT_URL_FOR_SELF =
"intent://example.com#Intent;"
+ "package="
+ SELF_PACKAGE_NAME
+ ";"
+ "action=android.intent.action.VIEW;"
+ "scheme=http;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(YOUTUBE_URL)
+ ";end"
+ "end;";
private static final String PLUS_STREAM_URL = "https://plus.google.com/stream";
private static final String CALENDAR_URL = "http://www.google.com/calendar";
private static final String KEEP_URL = "http://www.google.com/keep";
private static final String TEXT_APP_1_PACKAGE_NAME = "text_app_1";
private static final String TEXT_APP_2_PACKAGE_NAME = "text_app_2";
private static final String WEBAPK_SCOPE = "https://www.template.com";
private static final String WEBAPK_PACKAGE_PREFIX = "org.chromium.webapk";
private static final String WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".template";
private static final String INVALID_WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".invalid";
private static final String SELF_SCHEME = "selfscheme";
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@Mock private IncognitoDialogDelegate mIncognitoDialogDelegateMock;
@Mock private WindowAndroid mWindowAndroidMock;
private Context mContext;
private FakeModalDialogManager mModalDialogManager;
private final TestExternalNavigationDelegate mDelegate;
private ExternalNavigationHandlerForTesting mUrlHandler;
private Context mRealApplicationContext;
public ExternalNavigationHandlerTest() {
mDelegate = new TestExternalNavigationDelegate();
}
@Before
public void setUp() {
mRealApplicationContext = ContextUtils.getApplicationContext();
mContext = new TestContext(InstrumentationRegistry.getTargetContext(), mDelegate);
mModalDialogManager = new FakeModalDialogManager(ModalDialogManager.ModalDialogType.APP);
ContextUtils.initApplicationContextForTests(mContext);
mDelegate.setContext(mContext);
when(mWindowAndroidMock.getModalDialogManager()).thenReturn(mModalDialogManager);
mDelegate.setWindowAndroid(mWindowAndroidMock);
mUrlHandler = new ExternalNavigationHandlerForTesting(mDelegate);
NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
}
private RedirectHandler redirectHandlerForLinkClick() {
RedirectHandler handler = RedirectHandler.create();
handler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
return handler;
}
@Test
@SmallTest
public void testStartActivityToTrustedPackageWithoutUserGesture() {
IntentActivity filter = new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME);
filter.setIsNotSpecialized(true);
mDelegate.add(filter);
RedirectHandler handler = RedirectHandler.create();
handler.updateNewUrlLoading(
PageTransition.CLIENT_REDIRECT, false, false, 0, 0, false, true);
checkUrl(YOUTUBE_URL, handler).expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mDelegate.setIsCallingAppTrusted(true);
checkUrl(YOUTUBE_URL, handler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testOrdinaryIncognitoUri() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// http://crbug.com/587306: Don't prompt the user for capturing URLs in incognito, just keep
// it within the browser.
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testChromeReferrer() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// http://crbug.com/159153: Don't override http or https URLs from the NTP or bookmarks.
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withReferrer("chrome://about")
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl("tel:012345678", redirectHandlerForLinkClick())
.withReferrer("chrome://about")
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testForwardBackNavigation() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler handler = new RedirectHandler();
handler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FORWARD_BACK, false, true, 0, 0, false, true);
// http://crbug.com/164194. We shouldn't show the intent picker on
// forwards or backwards navigations.
checkUrl(YOUTUBE_URL, handler)
.withPageTransition(PageTransition.LINK | PageTransition.FORWARD_BACK)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler handler = new RedirectHandler();
handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT, false, true, 0, 0, false, true);
handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT, true, true, 0, 0, false, true);
// http://crbug.com/181186: We need to show the intent picker when we receive a redirect
// following a form submit. OAuth of native applications rely on this.
checkUrl("market://1234", handler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// If the page matches the referrer, then continue loading in Chrome.
checkUrl("http://youtube.com://", handler)
.withReferrer(YOUTUBE_URL)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// If the page does not match the referrer, then prompt an intent.
checkUrl("http://youtube.com://", handler)
.withReferrer("http://google.com")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT, false, true, 0, 0, false, true);
// It doesn't make sense to allow intent picker without redirect, since form data
// is not encoded in the intent (although, in theory, it could be passed in as
// an extra data in the intent).
checkUrl("http://youtube.com://", handler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit_NoUserGesture() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mUrlHandler.mExpectingMessage = true;
RedirectHandler handler = new RedirectHandler();
handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT, false, false, 0, 0, false, true);
handler.updateNewUrlLoading(PageTransition.FORM_SUBMIT, true, false, 0, 0, false, true);
// If the redirect is not associated with a user gesture, then continue loading in Chrome.
checkUrl("market://1234", handler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, IGNORE);
// Callback won't have been run for the Message.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
checkUrl("http://youtube.com", handler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit_NoUserGesture_OnIntentRedirectChain() throws Exception {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateIntent(
Intent.parseUri("http://example.test", Intent.URI_INTENT_SCHEME),
!IS_CUSTOM_TAB_INTENT,
!SEND_TO_EXTERNAL_APPS,
!INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FROM_API, false, false, 0, 0, true, false);
redirectHandler.updateNewUrlLoading(
PageTransition.FORM_SUBMIT, false, false, 0, 0, false, true);
// If the redirect is not associated with a user gesture but came from an incoming intent,
// then allow those to launch external intents.
checkUrl("market://1234", redirectHandler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(false)
.withHasUserGesture(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
redirectHandler.updateNewUrlLoading(
PageTransition.FORM_SUBMIT, true, false, 0, 0, false, true);
checkUrl("http://youtube.com", redirectHandler)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testOrdinary_disableExternalIntentRequestsForUrl() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.setDisableExternalIntentRequests(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
private void assertOverrideUrlToNavigateToTab() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
mDelegate.setCanResolveActivityForExternalSchemes(true);
}
@Test
@SmallTest
public void testShouldReturnAsActivityResult_externalIntent() {
checkUrl("tel:012345678", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
mDelegate.setShouldDisableAllExternalIntents(true);
// With #shouldDisableAllExternalIntents() returning true,
// #OVERRIDE_WITH_EXTERNAL_INTENT is replaced with #NO_OVERRIDE.
checkUrl("tel:012345678", redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mDelegate.setShouldReturnAsActivityResult(true);
checkUrl("tel:012345678", redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testShouldReturnAsActivityResult_preserveOtherResultTypes() {
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
assertOverrideUrlToNavigateToTab();
mDelegate.setShouldDisableAllExternalIntents(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
assertOverrideUrlToNavigateToTab();
mDelegate.setShouldReturnAsActivityResult(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
assertOverrideUrlToNavigateToTab();
}
@Test
@SmallTest
public void testIgnore() {
// Ensure the following URLs are not broadcast for external navigation.
String urlsToIgnore[] =
new String[] {
"about:test",
"content:test", // Content URLs should not be exposed outside of Chrome.
"chrome://history",
"chrome-native://newtab",
"devtools://foo",
"intent:chrome-urls#Intent;package=com.android.chrome;scheme=about;end;",
"intent:chrome-urls#Intent;package=com.android.chrome;scheme=chrome;end;",
"intent://com.android.chrome.FileProvider/foo.html#Intent;scheme=content;end;",
"intent:///x.mhtml#Intent;package=com.android.chrome;action=android.intent.action.VIEW;scheme=file;end;"
};
for (String url : urlsToIgnore) {
checkUrl(url, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(url, redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
}
@Test
@SmallTest
public void testPageTransitionType() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// Non-link page transition type are ignored.
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0, false, false);
// http://crbug.com/143118 - Don't show the picker for directly typed URLs, unless
// the URL results in a redirect.
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.RELOAD, false, false, 0, 0, false, false);
// http://crbug.com/162106 - Don't show the picker on reload.
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(PageTransition.RELOAD)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testWtai() {
// These two cases are currently unimplemented.
checkUrl("wtai://wp/sd;0123456789", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
checkUrl("wtai://wp/ap;0123456789", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
// Ignore other WTAI urls.
checkUrl("wtai://wp/invalid", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
}
@Test
@SmallTest
public void testRedirectToMarketWithReferrer() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_APP_NOT_INSTALLED_WITH_MARKET_REFERRER, redirectHandlerForLinkClick())
.withReferrer(KEEP_URL)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Uri uri = mUrlHandler.mStartActivityIntent.getData();
Assert.assertEquals("market", uri.getScheme());
Assert.assertEquals(Uri.decode(ENCODED_MARKET_REFERRER), uri.getQueryParameter("referrer"));
Assert.assertEquals(
Uri.parse(KEEP_URL),
mUrlHandler.mStartActivityIntent.getParcelableExtra(Intent.EXTRA_REFERRER));
}
@Test
@SmallTest
public void testRedirectToMarketWithoutReferrer() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_APP_NOT_INSTALLED_DEFAULT_MARKET_REFERRER, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Uri uri = mUrlHandler.mStartActivityIntent.getData();
Assert.assertEquals("market", uri.getScheme());
Assert.assertEquals(getPackageName(), uri.getQueryParameter("referrer"));
}
@Test
@SmallTest
public void testExternalUri() {
checkUrl("tel:012345678", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testTypedRedirectToExternalProtocol() {
mUrlHandler.mExpectingMessage = true;
RedirectHandler redirectHandler = RedirectHandler.create();
// http://crbug.com/169549
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, true, false, 0, 0, false, false);
checkUrl("market://1234", redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, IGNORE);
// Callback won't have been run for the Message.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
// http://crbug.com/709217
redirectHandler.updateNewUrlLoading(
PageTransition.FROM_ADDRESS_BAR, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
PageTransition.FROM_ADDRESS_BAR, true, false, 0, 0, false, false);
checkUrl("market://1234", redirectHandler)
.withPageTransition(PageTransition.FROM_ADDRESS_BAR)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, IGNORE);
// Callback won't have been run for the Message.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
// If a user types an external protocol, it may as well ask to leave Chrome.
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0, false, false);
checkUrl("market://1234", redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, IGNORE);
// Callback won't have been run for the Message.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testIncomingIntentRedirect() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
int transitionTypeIncomingIntent = PageTransition.LINK | PageTransition.FROM_API;
// http://crbug.com/149218
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// http://crbug.com/170925
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// http://crbug.com/1310795
mDelegate.setIsChromeAppInForeground(false);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.withChromeAppInForegroundRequired(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIncomingIntentRedirect_FallbackUrl() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.setIsChromeAppInForeground(false);
int transitionTypeIncomingIntent = PageTransition.LINK | PageTransition.FROM_API;
// http://crbug.com/1310795
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.withChromeAppInForegroundRequired(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
}
@Test
@SmallTest
public void testIntentScheme() {
String url =
"intent:wtai://wp/#Intent;action=android.settings.SETTINGS;"
+ "component=package/class;end";
String urlWithNullData =
"intent:#Intent;package=com.google.zxing.client.android;"
+ "action=android.settings.SETTINGS;end";
checkUrl(url, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
checkUrl(urlWithNullData, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
// http://crbug.com/1254422
@Test
@SmallTest
public void testIntentSelectorRemoved() {
String urlWithSel =
"intent:wtai://wp/#Intent;SEL;action=android.settings.SETTINGS;"
+ "component=package/class;end";
checkUrl(urlWithSel, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNull(mUrlHandler.mStartActivityIntent.getSelector());
}
@Test
@SmallTest
public void testYouTubePairingCode() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
String mobileUrl = "http://m.youtube.com/watch?v=1234&pairingCode=5678";
int transitionTypeIncomingIntent = PageTransition.LINK | PageTransition.FROM_API;
final String[] goodUrls = {
mobileUrl,
"http://youtube.com?pairingCode=xyz",
"http://youtube.com/tv?pairingCode=xyz",
"http://youtube.com/watch?v=1234&version=3&autohide=1&pairingCode=xyz",
"http://youtube.com/watch?v=1234&pairingCode=xyz&version=3&autohide=1"
};
final String[] badUrls = {
"http://youtube.com.foo.com/tv?pairingCode=xyz",
"http://youtube.com.foo.com?pairingCode=xyz",
"http://youtube.com&pairingCode=xyz",
"http://youtube.com/watch?v=1234#pairingCode=xyz"
};
// Make sure we don't override when faced with valid pairing code URLs.
for (String url : goodUrls) {
Assert.assertTrue(mUrlHandler.isYoutubePairingCode(new GURL(url)));
}
for (String url : badUrls) {
Assert.assertFalse(mUrlHandler.isYoutubePairingCode(new GURL(url)));
}
RedirectHandler handler = redirectHandlerForLinkClick();
// http://crbug/386600 - it makes no sense to switch activities for pairing code URLs.
checkUrl(mobileUrl, handler)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
handler.updateNewUrlLoading(PageTransition.LINK, true, true, 0, 0, false, true);
checkUrl(mobileUrl, handler)
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testInitialIntent() throws URISyntaxException {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
Intent ytIntent = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
// Ignore if url is redirected, transition type is IncomingIntent and a new intent doesn't
// have any new resolver.
redirectHandler.updateIntent(
ytIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// Do not ignore if a new intent has any new resolver.
redirectHandler.updateIntent(
fooIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Do not ignore if a new intent cannot be handled by Chrome.
redirectHandler.updateIntent(
fooIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl("intent://myownurl", redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testInitialIntentHeadingToChrome() throws URISyntaxException {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
fooIntent.setPackage(mContext.getPackageName());
int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
// Ignore if an initial Intent was heading to Chrome.
redirectHandler.updateIntent(
fooIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// Do not ignore if the URI has an external protocol.
redirectHandler.updateIntent(
fooIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl("market://1234", redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIntentForCustomTab() throws URISyntaxException {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
// In Custom Tabs, if the first url is not a redirect, stay in chrome.
Intent barIntent = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
barIntent.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(
barIntent, IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// In Custom Tabs, if the first url is a redirect, don't allow it to intent out.
Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
fooIntent.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(
fooIntent, IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// In Custom Tabs, if the external handler extra is present, intent out if the first
// url is a redirect.
Intent extraIntent2 = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
extraIntent2.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(
extraIntent2, IS_CUSTOM_TAB_INTENT, SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Intent extraIntent3 = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
extraIntent3.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(
extraIntent3, IS_CUSTOM_TAB_INTENT, SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(transTypeLinkFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// External intent for a user-initiated navigation should always be allowed.
redirectHandler.updateIntent(
fooIntent, IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeLinkFromIntent, false, false, 0, 0, false, false);
// Simulate a real user navigation.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK,
false,
true,
SystemClock.elapsedRealtime() + 1,
0,
false,
true);
checkUrl(YOUTUBE_URL, redirectHandler)
.withPageTransition(PageTransition.LINK)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testExternalRedirectForTwa() throws URISyntaxException {
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
// TWAs use AUTO_TOPLEVEL for metrics reasons.
int transTypeTopLevelFromIntent = PageTransition.AUTO_TOPLEVEL | PageTransition.FROM_API;
Intent intent = Intent.parseUri(IMDB_APP_INTENT_FOR_TOM_HANKS, Intent.URI_INTENT_SCHEME);
redirectHandler.updateIntent(
intent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
transTypeTopLevelFromIntent, false, false, 0, 0, true, false);
redirectHandler.updateNewUrlLoading(
transTypeTopLevelFromIntent, true, false, 0, 0, false, false);
checkUrl(IMDB_APP_INTENT_FOR_TOM_HANKS, redirectHandler)
.withPageTransition(transTypeTopLevelFromIntent)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testCCTIntentUriDoesNotFireCCTAndLoadInChrome_InIncognito() throws Exception {
mUrlHandler.mResolveInfoContainsSelf = true;
mDelegate.setCanLoadUrlInTab(false);
checkUrl(INTENT_URL_FOR_SELF_CUSTOM_TABS, redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals("http://example.com/", mUrlHandler.mNewUrlAfterClobbering);
}
@Test
@SmallTest
public void testCCTIntentUriFiresCCT_InRegular() throws Exception {
checkUrl(INTENT_URL_FOR_SELF_CUSTOM_TABS, redirectHandlerForLinkClick())
.withIsIncognito(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
}
@Test
@SmallTest
public void testChromeIntentUriDoesNotFireAndLoadsInChrome_InIncognito() throws Exception {
mUrlHandler.mResolveInfoContainsSelf = true;
mDelegate.setCanLoadUrlInTab(false);
checkUrl(INTENT_URL_FOR_SELF, redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals("http://example.com/", mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mResolveInfoContainsSelf = false;
checkUrl(INTENT_URL_FOR_SELF, redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(YOUTUBE_URL, mUrlHandler.mNewUrlAfterClobbering);
}
@Test
@SmallTest
public void testIsIntentToInstantApp() {
// Check that the delegate correctly distinguishes instant app intents from others.
String instantAppIntentUrlPrefix = "intent://buzzfeed.com/tasty#Intent;scheme=http;";
// Check that Supervisor is detected by action even without package.
for (String action : ExternalNavigationHandler.INSTANT_APP_START_ACTIONS) {
String intentUrl = instantAppIntentUrlPrefix + "action=" + action + ";end";
checkUrl(intentUrl, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
String intentUrl =
instantAppIntentUrlPrefix
+ "package="
+ ExternalNavigationHandler.INSTANT_APP_SUPERVISOR_PKG
+ ";end";
checkUrl(intentUrl, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testFallbackUrl_IntentResolutionSucceeds() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Intent invokedIntent = mUrlHandler.mStartActivityIntent;
Assert.assertEquals(IMDB_APP_INTENT_FOR_TOM_HANKS, invokedIntent.getData().toString());
Assert.assertNull(
"The invoked intent should not have browser_fallback_url\n",
invokedIntent.getStringExtra(ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL));
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
Assert.assertNull(mUrlHandler.mReferrerUrlForClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_IntentResolutionSucceedsInIncognito() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
mUrlHandler.mCanShowIncognitoDialog = true;
// Expect that the user is prompted to leave incognito mode.
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
Assert.assertNull(mUrlHandler.mReferrerUrlForClobbering);
// Callback won't have been run for the mocked AlertDialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testFallbackUrl_FallbackToWebApk() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
}
@Test
@SmallTest
public void testFallbackUrl_DontFallbackToWebApkMultipleHandlers() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, TEXT_APP_1_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(SEARCH_RESULT_URL_FOR_TOM_HANKS, mUrlHandler.mReferrerUrlForClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_IntentResolutionFails() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
// When intent resolution fails, we should not start an activity, but instead clobber
// the current tab.
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(SEARCH_RESULT_URL_FOR_TOM_HANKS, mUrlHandler.mReferrerUrlForClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_FallbackToMarketApp() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
String intent =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ "https://play.google.com/store/apps/details?id=com.imdb.mobile"
+ "&referrer=mypage;end";
checkUrl(intent, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=mypage",
mUrlHandler.mStartActivityIntent.getDataString());
String intentNoRef =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ "https://play.google.com/store/apps/details?id=com.imdb.mobile;end";
checkUrl(intentNoRef, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=" + getPackageName(),
mUrlHandler.mStartActivityIntent.getDataString());
String intentBadUrl =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ "https://play.google.com/store/search?q=pub:imdb;end";
checkUrl(intentBadUrl, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
}
@Test
@MediumTest
public void testFallbackUrl_FallbackToMarketApp_Incognito() {
// Test uses an ActivityMonitor to catch the outgoing intent.
mUrlHandler.sendIntentsForReal();
IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
filter.addCategory(Intent.CATEGORY_BROWSABLE);
filter.addDataScheme("market");
ActivityMonitor monitor =
InstrumentationRegistry.getInstrumentation()
.addMonitor(
filter,
new Instrumentation.ActivityResult(Activity.RESULT_OK, null),
true);
Intent dummyIntent = new Intent(mRealApplicationContext, BlankUiTestActivity.class);
dummyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Activity activity =
InstrumentationRegistry.getInstrumentation().startActivitySync(dummyIntent);
mDelegate.setContext(activity);
mDelegate.setCanLoadUrlInTab(true);
try {
mDelegate.setCanResolveActivityForExternalSchemes(false);
String playUrl =
"https://play.google.com/store/apps/details?id=com.imdb.mobile"
+ "&referrer=mypage";
String intent =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(playUrl, null)
+ ";end;";
mUrlHandler.mCanShowIncognitoDialog = true;
ThreadUtils.runOnUiThreadBlocking(
() -> {
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mIncognitoDialogDelegate.cancelDialog();
});
// Cancel callback is posted, so continue after posting to the task queue.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(playUrl, mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mNewUrlAfterClobbering = null;
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
mUrlHandler.mIncognitoDialogDelegate.performClick(
ModalDialogProperties.ButtonType.POSITIVE);
});
// Click callback is posted, so continue after posting to the task queue.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(1, monitor.getHits());
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=mypage",
mUrlHandler.mStartActivityIntent.getDataString());
});
} finally {
activity.finish();
InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
}
}
private void doTestFallbackUrl_ChromeCanHandle_Incognito(final boolean clearRedirectHandler) {
mDelegate.add(new IntentActivity("https", "package"));
Intent dummyIntent = new Intent(mRealApplicationContext, BlankUiTestActivity.class);
dummyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Activity activity =
InstrumentationRegistry.getInstrumentation().startActivitySync(dummyIntent);
mDelegate.setContext(activity);
mDelegate.setCanLoadUrlInTab(true);
try {
String intent =
"intent://example.com#Intent;scheme=https;"
+ "S.browser_fallback_url=http%3A%2F%2Fgoogle.com;end";
mUrlHandler.mResolveInfoContainsSelf = true;
mUrlHandler.mCanShowIncognitoDialog = true;
ThreadUtils.runOnUiThreadBlocking(
() -> {
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(intent, redirectHandler)
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
if (clearRedirectHandler) redirectHandler.clear();
mUrlHandler.mIncognitoDialogDelegate.cancelDialog();
});
// Cancel callback is posted, so continue after posting to the task queue.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
"https://example.com/", mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mNewUrlAfterClobbering = null;
mUrlHandler.mResolveInfoContainsSelf = false;
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(intent, redirectHandler)
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
if (clearRedirectHandler) redirectHandler.clear();
mUrlHandler.mIncognitoDialogDelegate.cancelDialog();
});
// Click callback is posted, so continue after posting to the task queue.
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
"http://google.com/", mUrlHandler.mNewUrlAfterClobbering);
});
} finally {
activity.finish();
}
}
@Test
@MediumTest
public void testFallbackUrl_ChromeCanHandle_Incognito() {
doTestFallbackUrl_ChromeCanHandle_Incognito(false);
}
// https://crbug.com/1302566
@Test
@MediumTest
public void testFallbackUrl_ChromeCanHandle_Incognito_ClearRedirectHandler() {
doTestFallbackUrl_ChromeCanHandle_Incognito(true);
}
@Test
@MediumTest
public void testFallbackUrl_FallbackToMarketApp_Incognito_DelegateHandleDialogPresentation() {
// Test uses an ActivityMonitor to catch the outgoing intent.
mUrlHandler.sendIntentsForReal();
IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
filter.addCategory(Intent.CATEGORY_BROWSABLE);
filter.addDataScheme("market");
ActivityMonitor monitor =
InstrumentationRegistry.getInstrumentation()
.addMonitor(
filter,
new Instrumentation.ActivityResult(Activity.RESULT_OK, null),
true);
Intent dummyIntent = new Intent(mRealApplicationContext, BlankUiTestActivity.class);
dummyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Activity activity =
InstrumentationRegistry.getInstrumentation().startActivitySync(dummyIntent);
mDelegate.setContext(activity);
mDelegate.setCanLoadUrlInTab(true);
mDelegate.setShouldPresentLeavingIncognitoDialog(true);
try {
mDelegate.setCanResolveActivityForExternalSchemes(false);
String playUrl =
"https://play.google.com/store/apps/details?id=com.imdb.mobile"
+ "&referrer=mypage";
String intent =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ Uri.encode(playUrl, null)
+ ";end;";
mUrlHandler.mCanShowIncognitoDialog = true;
ThreadUtils.runOnUiThreadBlocking(
() -> {
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
// Verify that the incognito dialog was not shown.
Assert.assertNull(mUrlHandler.mIncognitoDialogDelegate);
// Verify that the delegate was given the opportunity to present the dialog.
Assert.assertNotNull(mDelegate.incognitoDialogUserDecisionCallback);
// Inform the handler that the user decided not to launch the intent and
// verify that the appropriate URL is navigated to in the browser.
mDelegate.incognitoDialogUserDecisionCallback.onResult(
Boolean.valueOf(false));
Assert.assertEquals(playUrl, mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mNewUrlAfterClobbering = null;
mDelegate.incognitoDialogUserDecisionCallback = null;
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
// Verify that the incognito dialog was not shown.
Assert.assertNull(mUrlHandler.mIncognitoDialogDelegate);
// Verify that the delegate was given the opportunity to present the dialog.
Assert.assertNotNull(mDelegate.incognitoDialogUserDecisionCallback);
// Inform the handler that the user decided to launch the intent and verify
// that the intent was launched.
mDelegate.incognitoDialogUserDecisionCallback.onResult(
Boolean.valueOf(true));
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(1, monitor.getHits());
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=mypage",
mUrlHandler.mStartActivityIntent.getDataString());
});
} finally {
activity.finish();
InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
}
}
@Test
@MediumTest
public void testFallbackUrl_ChromeCanHandle_Incognito_DelegateHandleDialogPresentation() {
mDelegate.add(new IntentActivity("https", "package"));
Intent dummyIntent = new Intent(mRealApplicationContext, BlankUiTestActivity.class);
dummyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Activity activity =
InstrumentationRegistry.getInstrumentation().startActivitySync(dummyIntent);
mDelegate.setContext(activity);
mDelegate.setCanLoadUrlInTab(true);
mDelegate.setShouldPresentLeavingIncognitoDialog(true);
try {
String intent =
"intent://example.com#Intent;scheme=https;"
+ "S.browser_fallback_url=http%3A%2F%2Fgoogle.com;end";
mUrlHandler.mResolveInfoContainsSelf = true;
mUrlHandler.mCanShowIncognitoDialog = true;
ThreadUtils.runOnUiThreadBlocking(
() -> {
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
// Verify that the incognito dialog was not shown.
Assert.assertNull(mUrlHandler.mIncognitoDialogDelegate);
// Verify that the delegate was given the opportunity to present the dialog.
Assert.assertNotNull(mDelegate.incognitoDialogUserDecisionCallback);
// Inform the handler that the user decided not to launch the intent and
// verify that the appropriate URL is navigated to in the browser.
mDelegate.incognitoDialogUserDecisionCallback.onResult(
Boolean.valueOf(false));
Assert.assertEquals(
"https://example.com/", mUrlHandler.mNewUrlAfterClobbering);
mUrlHandler.mNewUrlAfterClobbering = null;
mUrlHandler.mResolveInfoContainsSelf = false;
mDelegate.incognitoDialogUserDecisionCallback = null;
checkUrl(intent, redirectHandlerForLinkClick())
.withIsIncognito(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
// Verify that the incognito dialog was not shown.
Assert.assertNull(mUrlHandler.mIncognitoDialogDelegate);
// Verify that the delegate was given the opportunity to present the dialog.
Assert.assertNotNull(mDelegate.incognitoDialogUserDecisionCallback);
// Inform the handler that the user decided not to launch the intent and
// verify that the appropriate URL is navigated to in the browser.
mDelegate.incognitoDialogUserDecisionCallback.onResult(
Boolean.valueOf(false));
Assert.assertEquals(
"http://google.com/", mUrlHandler.mNewUrlAfterClobbering);
});
} finally {
activity.finish();
}
}
@Test
@MediumTest
public void testIncognitoAlertDialogDismissedOnNavigation() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
mUrlHandler.mCanShowIncognitoDialog = true;
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.withHasUserGesture(true)
.withIsIncognito(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO);
Assert.assertTrue(mUrlHandler.mStartIncognitoIntentCalled);
// Callback won't have been run with the mocked AlertDialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
Mockito.doReturn(true).when(mIncognitoDialogDelegateMock).isShowing();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0, false, true);
checkUrl(YOUTUBE_URL, redirectHandler)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
Mockito.verify(mIncognitoDialogDelegateMock).cancelDialog();
}
public void runIncognitoAlertDialogDismissedTest(
long navId, Runnable testCallback, boolean shouldDismiss) {
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
Intent dummyIntent = new Intent(mRealApplicationContext, BlankUiTestActivity.class);
dummyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Activity activity =
InstrumentationRegistry.getInstrumentation().startActivitySync(dummyIntent);
mDelegate.setContext(activity);
mDelegate.setCanLoadUrlInTab(true);
try {
mDelegate.setCanResolveActivityForExternalSchemes(true);
mUrlHandler.mCanShowIncognitoDialog = true;
ThreadUtils.runOnUiThreadBlocking(
() -> {
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.withHasUserGesture(true)
.withIsIncognito(true)
.withNavigationId(navId)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mNewUrlAfterClobbering);
});
IncognitoDialogDelegate delegateSpy = mUrlHandler.spyIncognitoDialogDelegate();
Mockito.doReturn(true).when(delegateSpy).isShowing();
ThreadUtils.runOnUiThreadBlocking(
() -> {
testCallback.run();
});
if (shouldDismiss) {
Mockito.verify(delegateSpy).cancelDialog();
} else {
Mockito.verify(delegateSpy, never()).cancelDialog();
// Dialog must be canceled before Activity finishes since the ModalDialogManager
// isn't hooked up.
delegateSpy.cancelDialog();
}
} finally {
activity.finish();
}
}
@Test
@MediumTest
public void testIncognitoAlertDialogDismissedOnRacyNavigation() {
int navId = 2;
runIncognitoAlertDialogDismissedTest(
navId,
() -> {
mUrlHandler.onNavigationFinished(navId - 1);
},
true);
}
@Test
@MediumTest
public void testIncognitoAlertDialogDismissedOnNewNavigation() {
int navId = 1;
runIncognitoAlertDialogDismissedTest(
navId,
() -> {
mUrlHandler.onNavigationStarted(navId + 1);
},
true);
}
@Test
@MediumTest
public void testIncognitoAlertDialogNotDismissedOnSameNavigation() {
int navId = 1;
runIncognitoAlertDialogDismissedTest(
navId,
() -> {
mUrlHandler.onNavigationStarted(navId);
mUrlHandler.onNavigationFinished(navId);
},
false);
}
@Test
@SmallTest
public void testFallbackUrl_SubframeFallbackToMarketApp() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
String intent =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ "https://play.google.com/store/apps/details?id=com.imdb.mobile"
+ "&referrer=mypage;end";
checkUrl(intent, redirectHandler)
.withIsMainFrame(false)
.withHasUserGesture(true)
.withPageTransition(PageTransition.LINK)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=mypage",
mUrlHandler.mStartActivityIntent.getDataString());
String fallbackUrl = "https://play.google.com/store/search?q=pub:imdb";
redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
String intentBadUrl =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ fallbackUrl
+ ";end";
checkUrl(intentBadUrl, redirectHandler)
.withIsMainFrame(false)
.withHasUserGesture(true)
.withPageTransition(PageTransition.LINK)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertEquals(fallbackUrl, mUrlHandler.mNewUrlAfterClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_RedirectToIntentToMarket() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl("http://goo.gl/abcdefg", redirectHandler)
.withPageTransition(PageTransition.LINK)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, true, 0, 0, false, true);
String realIntent =
"intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;"
+ "S."
+ ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL
+ "="
+ "https://play.google.com/store/apps/details?id=com.imdb.mobile"
+ "&referrer=mypage;end";
checkUrl(realIntent, redirectHandler)
.withPageTransition(PageTransition.LINK)
.withIsRedirect(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
"market://details?id=com.imdb.mobile&referrer=mypage",
mUrlHandler.mStartActivityIntent.getDataString());
}
@Test
@SmallTest
public void testFallbackUrl_FallbackForAutoSubframe() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.AUTO_SUBFRAME, true, false, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.withIsMainFrame(false)
.withHasUserGesture(false)
.withPageTransition(PageTransition.AUTO_SUBFRAME)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mUrlHandler.mNewUrlAfterClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_NoExternalFallbackWithoutGesture() {
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, WEBAPK_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.withHasUserGesture(false)
.withPageTransition(PageTransition.LINK)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
}
@Test
@SmallTest
public void testFallbackUrl_IntentResolutionFailsWithoutPackageName() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
// Fallback URL should work even when package name isn't given.
checkUrl(INTENT_URL_WITH_FALLBACK_URL_WITHOUT_PACKAGE_NAME, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(SEARCH_RESULT_URL_FOR_TOM_HANKS, mUrlHandler.mReferrerUrlForClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_FallbackShouldNotWarnOnIncognito() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(SEARCH_RESULT_URL_FOR_TOM_HANKS, mUrlHandler.mReferrerUrlForClobbering);
}
@Test
@SmallTest
public void testFallbackUrl_IgnoreJavascriptFallbackUrl() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
mUrlHandler.mCanShowIncognitoDialog = true;
// Will be redirected to market since package is given.
checkUrl(INTENT_URL_WITH_JAVASCRIPT_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsIncognito(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO);
Intent invokedIntent = mUrlHandler.mStartActivityInIncognitoIntent;
Assert.assertTrue(invokedIntent.getData().toString().startsWith("market://"));
Assert.assertEquals(null, mUrlHandler.mNewUrlAfterClobbering);
Assert.assertEquals(null, mUrlHandler.mReferrerUrlForClobbering);
// Callback won't have been run with the mocked AlertDialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testFallback_UseFallbackUrlForRedirectionFromTypedInUrl() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0, false, false);
checkUrl("http://goo.gl/abcdefg", redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL_WITHOUT_PACKAGE_NAME, redirectHandler)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
// Now the user opens a link.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIgnoreEffectiveRedirectFromIntentFallbackUrl() {
// We cannot resolve any intent, so fall-back URL will be used.
mDelegate.setCanResolveActivityForExternalSchemes(false);
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_CHAIN_FALLBACK_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
mDelegate.setCanResolveActivityForExternalSchemes(true);
// As a result of intent resolution fallback, we have clobberred the current tab and the
// sending site has learned that an app is not installed. In order to prevent chaining this
// and learning about more not-installed apps, even URLs that would otherwise successfully
// launch an app will use the fallback URL.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
// New user gesture.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIgnoreEffectiveRedirectFromUserTyping() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withPageTransition(PageTransition.TYPED)
.withIsRendererInitiated(false)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNavigationFromLinkWithoutUserGesture() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 0, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testChromeAppInBackground() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.setIsChromeAppInForeground(false);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNotChromeAppInForegroundRequired() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.setIsChromeAppInForeground(false);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withChromeAppInForegroundRequired(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testCreatesIntentsToOpenInNewTab() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
mUrlHandler = new ExternalNavigationHandlerForTesting(mDelegate);
ExternalNavigationParams params =
new ExternalNavigationParams.Builder(new GURL(YOUTUBE_MOBILE_URL), false)
.setOpenInNewTab(true)
.setIsMainFrame(true)
.setIsRendererInitiated(true)
.setRedirectHandler(redirectHandlerForLinkClick())
.build();
OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
Assert.assertEquals(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result.getResultType());
Assert.assertTrue(mUrlHandler.mStartActivityIntent != null);
Assert.assertTrue(
mUrlHandler.mStartActivityIntent.getBooleanExtra(
Browser.EXTRA_CREATE_NEW_TAB, false));
}
@Test
@SmallTest
public void testCanExternalAppHandleUrl() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.add(new IntentActivity("someapp", "someapp"));
Assert.assertTrue(mUrlHandler.canExternalAppHandleUrl(new GURL("someapp://someapp.com/")));
Assert.assertTrue(mUrlHandler.canExternalAppHandleUrl(new GURL("wtai://wp/mc;0123456789")));
Assert.assertTrue(
mUrlHandler.canExternalAppHandleUrl(
new GURL("intent:/#Intent;scheme=noapp;package=com.noapp;end")));
Assert.assertFalse(mUrlHandler.canExternalAppHandleUrl(new GURL("noapp://noapp.com/")));
}
@Test
@SmallTest
public void testPlusAppRefresh() {
mDelegate.add(new IntentActivity(PLUS_STREAM_URL, "plus"));
checkUrl(PLUS_STREAM_URL, redirectHandlerForLinkClick())
.withReferrer(PLUS_STREAM_URL)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testSameDomainDifferentApps() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL, redirectHandlerForLinkClick())
.withReferrer(KEEP_URL)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testFormSubmitSameDomain() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL, redirectHandlerForLinkClick())
.withReferrer(KEEP_URL)
.withPageTransition(PageTransition.FORM_SUBMIT)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testBackgroundTabNavigation() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withIsBackgroundTabNavigation(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testBackgroundTabNavigationWithIntentLaunchesInBackgroundTabsAllowed() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withIsBackgroundTabNavigation(true)
.withAllowIntentLaunchesInBackgroundTabs(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testPdfDownloadHappensInChrome() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL + "/file.pdf", redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testIntentToPdfFileOpensApp() {
checkUrl(
"intent://yoursite.com/mypdf.pdf#Intent;action=VIEW;category=BROWSABLE;"
+ "scheme=http;package=com.adobe.reader;end;",
redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testUsafeIntentFlagsFiltered() {
checkUrl(
"intent:#Intent;package=com.test.package;launchFlags=0x7FFFFFFF;end;",
redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
ExternalNavigationHandler.ALLOWED_INTENT_FLAGS,
mUrlHandler.mStartActivityIntent.getFlags());
}
@Test
@SmallTest
public void testIntentWithFileSchemeFiltered() {
checkUrl(
"intent://#Intent;package=com.test.package;scheme=file;end;",
redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testIntentWithNoSchemeLaunched() {
checkUrl("intent:#Intent;package=com.test.package;end;", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIntentWithEmptySchemeLaunched() {
checkUrl(
"intent://#Intent;package=com.test.package;scheme=;end;",
redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIntentWithWeirdSchemeLaunched() {
checkUrl(
"intent://#Intent;package=com.test.package;scheme=w3irD;end;",
redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Schemes on Android are case-sensitive, so ensure the scheme is passed through as-is.
Assert.assertEquals("w3irD", mUrlHandler.mStartActivityIntent.getScheme());
}
@Test
@SmallTest
public void testIntentWithMissingReferrer() {
mDelegate.add(new IntentActivity("http://refertest.com", "refertest"));
mDelegate.add(new IntentActivity("https://refertest.com", "refertest"));
// http://crbug.com/702089: Don't override links within the same host/domain.
// This is an issue for HTTPS->HTTP because there's no referrer, so we fall back on the
// WebContents.lastCommittedUrl.
checkUrl("http://refertest.com", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
mUrlHandler.mLastCommittedUrl = new GURL("https://refertest.com");
checkUrl("https://refertest.com", redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testReferrerExtra() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
String referrer = "http://www.google.com/";
checkUrl(YOUTUBE_URL + ":90/foo/bar", redirectHandlerForLinkClick())
.withReferrer(referrer)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
Uri.parse(referrer),
mUrlHandler.mStartActivityIntent.getParcelableExtra(Intent.EXTRA_REFERRER));
}
@Test
@SmallTest
public void testNavigationFromReload() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.RELOAD, true, false, 1, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNavigationWithForwardBack() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.FORM_SUBMIT | PageTransition.FORWARD_BACK,
true,
false,
1,
0,
false,
false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SuppressLint("SdCardPath")
@SmallTest
@MaxAndroidSdkLevel(Build.VERSION_CODES.S)
public void testFileAccessHtml() {
String fileUrl = "file:///sdcard/Downloads/test.html";
mUrlHandler.mShouldRequestFileAccess = false;
// Verify no overrides if file access is allowed (under different load conditions).
checkUrl(fileUrl, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.RELOAD)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mUrlHandler.mShouldRequestFileAccess = true;
// Verify that the file intent action is triggered if file access is not allowed.
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_FILE);
// Callback won't have been run for the Permission check.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
@MinAndroidSdkLevel(33) // TODO(twellington): Replace with version code when available.
public void testFileAccessHtml_AndroidT() {
String fileUrl = "file:///sdcard/Downloads/test.html";
mUrlHandler.mShouldRequestFileAccess = true;
// Verify that the file intent is not triggered if the mime type can't be handled.
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SuppressLint("SdCardPath")
@SmallTest
@MaxAndroidSdkLevel(
value = Build.VERSION_CODES.S,
reason = "T changed external storage permissions so we no longer handle this intent.")
public void testFileAccessImage() {
String fileUrl = "file://file.png";
mUrlHandler.mShouldRequestFileAccess = false;
// Verify no overrides if file access is allowed (under different load conditions).
checkUrl(fileUrl, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.RELOAD)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mUrlHandler.mShouldRequestFileAccess = true;
// Verify that the file intent action is triggered if file access is not allowed.
checkUrl(fileUrl, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_FILE);
// Callback won't have been run for the Permission dialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testSms_DispatchIntentToDefaultSmsApp() {
final String referer = "https://www.google.com/";
mDelegate.add(new IntentActivity("sms", TEXT_APP_1_PACKAGE_NAME));
mDelegate.add(new IntentActivity("sms", TEXT_APP_2_PACKAGE_NAME));
mUrlHandler.defaultSmsPackageName = TEXT_APP_2_PACKAGE_NAME;
checkUrl("sms:+012345678?body=hello%20there", redirectHandlerForLinkClick())
.withReferrer(referer)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(TEXT_APP_2_PACKAGE_NAME, mUrlHandler.mStartActivityIntent.getPackage());
}
@Test
@SmallTest
public void testSms_DefaultSmsAppDoesNotHandleIntent() {
final String referer = "https://www.google.com/";
mDelegate.add(new IntentActivity("sms", TEXT_APP_1_PACKAGE_NAME));
mDelegate.add(new IntentActivity("sms", TEXT_APP_2_PACKAGE_NAME));
// Note that this package does not resolve the intent.
mUrlHandler.defaultSmsPackageName = "text_app_3";
checkUrl("sms:+012345678?body=hello%20there", redirectHandlerForLinkClick())
.withReferrer(referer)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Assert.assertNull(mUrlHandler.mStartActivityIntent.getPackage());
}
@Test
@SmallTest
public void testSms_DispatchIntentSchemedUrlToDefaultSmsApp() {
final String referer = "https://www.google.com/";
mDelegate.add(new IntentActivity("sms", TEXT_APP_1_PACKAGE_NAME));
mDelegate.add(new IntentActivity("sms", TEXT_APP_2_PACKAGE_NAME));
mUrlHandler.defaultSmsPackageName = TEXT_APP_2_PACKAGE_NAME;
checkUrl(
"intent://012345678?body=hello%20there/#Intent;scheme=sms;end",
redirectHandlerForLinkClick())
.withReferrer(referer)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Assert.assertEquals(TEXT_APP_2_PACKAGE_NAME, mUrlHandler.mStartActivityIntent.getPackage());
}
/**
* Test that tapping a link which falls solely in the scope of a WebAPK launches a WebAPK
* without showing the intent picker.
*/
@Test
@SmallTest
public void testLaunchWebApk_BypassIntentPicker() {
mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME));
checkUrl(WEBAPK_SCOPE, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
}
/**
* Test that tapping a link which falls in the scope of multiple intent handlers, one of which
* is a WebAPK, shows the intent picker.
*/
@Test
@SmallTest
public void testLaunchWebApk_ShowIntentPickerMultipleIntentHandlers() {
final String scope = "https://www.webapk.with.native.com";
mDelegate.add(new IntentActivity(scope, WEBAPK_PACKAGE_PREFIX + ".with.native"));
mDelegate.add(new IntentActivity(scope, "com.webapk.with.native.android"));
checkUrl(scope, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
/**
* Test that tapping a link which falls solely into the scope of a different WebAPK launches a
* WebAPK without showing the intent picker.
*/
@Test
@SmallTest
public void testLaunchWebApk_BypassIntentPickerFromAnotherWebApk() {
final String scope1 = "https://www.webapk.with.native.com";
final String scope1WebApkPackageName = WEBAPK_PACKAGE_PREFIX + ".with.native";
final String scope1NativeAppPackageName = "com.webapk.with.native.android";
final String scope2 = "https://www.template.com";
mDelegate.add(new IntentActivity(scope1, scope1WebApkPackageName));
mDelegate.add(new IntentActivity(scope1, scope1NativeAppPackageName));
mDelegate.add(new IntentActivity(scope2, WEBAPK_PACKAGE_NAME));
mDelegate.setReferrerWebappPackageName(scope1WebApkPackageName);
checkUrl(scope2, redirectHandlerForLinkClick())
.withReferrer(scope1)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
}
/**
* Test that a link which falls into the scope of an invalid WebAPK (e.g. it was incorrectly
* signed) does not get any special WebAPK handling. The first time that the user taps on the
* link, the intent picker should be shown.
*/
@Test
@SmallTest
public void testLaunchWebApk_ShowIntentPickerInvalidWebApk() {
mDelegate.add(new IntentActivity(WEBAPK_SCOPE, INVALID_WEBAPK_PACKAGE_NAME));
checkUrl(WEBAPK_SCOPE, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
/**
* Tests that a WebAPK isn't launched from an initial Intent if the delegate doesn't say it
* should.
*/
@Test
@SmallTest
public void testLaunchWebApk_InitialIntent_DelegateReturnsFalse() {
mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME));
int transitionTypeIncomingIntent = PageTransition.LINK | PageTransition.FROM_API;
checkUrl(WEBAPK_SCOPE, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
/** Tests that a WebAPK is launched from an initial Intent if the delegate says it should. */
@Test
@SmallTest
public void testLaunchWebApk_InitialIntent_DelegateReturnsTrue() {
mDelegate.add(new IntentActivity(WEBAPK_SCOPE, WEBAPK_PACKAGE_NAME));
mDelegate.setShouldLaunchWebApksOnInitialIntent(true);
int transitionTypeIncomingIntent = PageTransition.LINK | PageTransition.FROM_API;
checkUrl(WEBAPK_SCOPE, redirectHandlerForLinkClick())
.withPageTransition(transitionTypeIncomingIntent)
.withIsRendererInitiated(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, START_WEBAPK);
}
@Test
@SmallTest
public void testMarketIntent_MarketInstalled() {
checkUrl("market://1234", redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mUrlHandler.mStartActivityIntent);
Assert.assertTrue(mUrlHandler.mStartActivityIntent.getScheme().startsWith("market"));
}
@Test
@SmallTest
public void testMarketIntent_MarketNotInstalled() {
mDelegate.setCanResolveActivityForMarket(false);
checkUrl("market://1234", redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
Assert.assertNull(mUrlHandler.mStartActivityIntent);
}
@Test
@SmallTest
public void testMarketIntent_ShowDialogIncognitoMarketInstalled() {
mUrlHandler.mCanShowIncognitoDialog = true;
checkUrl("market://1234", redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO);
Assert.assertTrue(mUrlHandler.mStartIncognitoIntentCalled);
// Callback won't have been run with the mocked AlertDialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testMarketIntent_DontShowDialogIncognitoMarketNotInstalled() {
mDelegate.setCanResolveActivityForMarket(false);
checkUrl("market://1234", redirectHandlerForLinkClick())
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
Assert.assertFalse(mUrlHandler.mStartIncognitoIntentCalled);
}
@Test
@SmallTest
public void testUserGesture_Regular() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withHasUserGesture(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled);
Assert.assertFalse(mUrlHandler.mStartIncognitoIntentCalled);
}
@Test
@SmallTest
public void testUserGesture_Incognito() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
mUrlHandler.mCanShowIncognitoDialog = true;
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withHasUserGesture(true)
.withIsIncognito(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION, START_INCOGNITO);
Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled);
Assert.assertTrue(mUrlHandler.mStartIncognitoIntentCalled);
// Callback won't have been run with the mocked AlertDialog.
mUrlHandler.mAsyncActionCallback.onResult(AsyncActionTakenParams.forNoAction());
}
@Test
@SmallTest
public void testRendererInitiated() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsRendererInitiated(true)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertTrue(mDelegate.maybeSetRequestMetadataCalled);
}
@Test
@SmallTest
public void testIsDownload_noSystemDownloadManager() {
Assert.assertTrue(
"pdf should be a download, no viewer in Android Chrome",
mUrlHandler.isPdfDownload(new GURL("http://somesampeleurldne.com/file.pdf")));
Assert.assertFalse(
"URL is not a file, but web page",
mUrlHandler.isPdfDownload(new GURL("http://somesampleurldne.com/index.html")));
Assert.assertFalse(
"URL is not a file url",
mUrlHandler.isPdfDownload(
new GURL("http://somesampeleurldne.com/not.a.real.extension")));
Assert.assertFalse(
"URL is an image, can be viewed in Chrome",
mUrlHandler.isPdfDownload(new GURL("http://somesampleurldne.com/image.jpg")));
Assert.assertFalse(
"URL is a text file can be viewed in Chrome",
mUrlHandler.isPdfDownload(new GURL("http://somesampleurldne.com/copy.txt")));
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_NoResolveInfo() {
String packageName = "";
List<ResolveInfo> resolveInfos = new ArrayList<ResolveInfo>();
Assert.assertEquals(
0,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_NoPathOrAuthority() {
String packageName = "";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
0,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_WithPath() {
String packageName = "";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
info.filter.addDataPath("somepath", 2);
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
1,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_WithAuthority() {
String packageName = "";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
info.filter.addDataAuthority("http://www.google.com", "80");
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
1,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_WithAuthority_Wildcard_Host() {
String packageName = "";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
info.filter.addDataAuthority("*", null);
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
0,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
ResolveInfo infoWildcardSubDomain = new ResolveInfo();
infoWildcardSubDomain.filter = new IntentFilter();
infoWildcardSubDomain.filter.addDataAuthority("http://*.google.com", "80");
List<ResolveInfo> resolveInfosWildcardSubDomain = makeResolveInfos(infoWildcardSubDomain);
Assert.assertEquals(
1,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfosWildcardSubDomain, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_WithTargetPackage_Matching() {
String packageName = "com.android.chrome";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
info.filter.addDataAuthority("http://www.google.com", "80");
info.activityInfo = new ActivityInfo();
info.activityInfo.packageName = packageName;
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
1,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testIsPackageSpecializedHandler_WithTargetPackage_NotMatching() {
String packageName = "com.android.chrome";
ResolveInfo info = new ResolveInfo();
info.filter = new IntentFilter();
info.filter.addDataAuthority("http://www.google.com", "80");
info.activityInfo = new ActivityInfo();
info.activityInfo.packageName = "com.foo.bar";
List<ResolveInfo> resolveInfos = makeResolveInfos(info);
Assert.assertEquals(
0,
ExternalNavigationHandler.getSpecializedHandlersWithFilter(
resolveInfos, packageName)
.size());
}
@Test
@SmallTest
public void testExceptions() {
// Test that we don't crash under various bad intent URIs.
String numberFormatException = "intent://foo#Intent;scheme=https;i.FOO=0.1;end";
String uriSyntaxException = "intent://foo#Intent;scheme=https;invalid=asdf;end";
String indexOutOfBoundsException = "intent://foo#Intent;scheme=https;c.%;end";
checkUrl(numberFormatException, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(uriSyntaxException, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(indexOutOfBoundsException, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
Assert.assertFalse(mUrlHandler.canExternalAppHandleUrl(new GURL(numberFormatException)));
Assert.assertFalse(mUrlHandler.canExternalAppHandleUrl(new GURL(uriSyntaxException)));
Assert.assertFalse(
mUrlHandler.canExternalAppHandleUrl(new GURL(indexOutOfBoundsException)));
}
@Test
@SmallTest
public void testUrlIntentToOtherBrowser() {
mDelegate.setResolvesToOtherBrowser(true);
String unsafeUrls[] =
new String[] {
"intent:#Intent;S.EXTRA_HIDDEN_URL=encodedUrl;action=CUSTOM.ACTION;end",
"intent:#Intent;S.EXTRA_HIDDEN_URL=encodedUrl;end",
"intent://example.com#Intent;scheme=https;action=CUSTOM.ACTION;end",
"intent://example.com#Intent;scheme=https;end",
"intent:example.com#Intent;end"
};
for (String url : unsafeUrls) {
checkUrl(url, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.LINK)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertTrue(mUrlHandler.mRequiresIntentChooser);
}
}
@Test
@SmallTest
public void testSafeIntentToOtherBrowser() throws Exception {
mDelegate.setResolvesToOtherBrowser(true);
String intent =
"intent:#Intent;action=ACTION.PROMO;package=" + OTHER_BROWSER_PACKAGE + ";end";
checkUrl(intent, redirectHandlerForLinkClick())
.withPageTransition(PageTransition.LINK)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertFalse(mUrlHandler.mRequiresIntentChooser);
}
@Test
@SmallTest
public void testSuppressDisambiguationDialog() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
mDelegate.setWillResolveToDisambiguationDialog(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
mDelegate.setShouldAvoidDisambiguationDialog(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
checkUrl(INTENT_URL_WITH_FALLBACK_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testSetTargetPackageName() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNull(mUrlHandler.mStartActivityIntent.getPackage());
mDelegate.setTargetPackageName("target.package");
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(null, mUrlHandler.mStartActivityIntent.getPackage());
mDelegate.setIsCallingAppTrusted(true);
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals("target.package", mUrlHandler.mStartActivityIntent.getPackage());
}
@Test
@SmallTest
public void testBlockNonExportedActivity_Self() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, SELF_PACKAGE_NAME, false));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testEmbedderInitiatedNavigationsLeaveBrowser() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.AUTO_BOOKMARK, false, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withIsRendererInitiated(false)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mDelegate.setShouldEmbedderInitiatedNavigationsStayInBrowser(false);
checkUrl(YOUTUBE_URL, redirectHandler)
.withIsRendererInitiated(false)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testExpiredNavigationChain() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
AtomicBoolean isExpired = new AtomicBoolean(false);
RedirectHandler redirectHandler =
new RedirectHandler() {
@Override
public boolean isNavigationChainExpired() {
return isExpired.get();
}
};
// User clicks a link.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
// Redirects to youtube with javascript simulated link click.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Page takes > 15 seconds to redirect.
isExpired.set(true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
mDelegate.setIsCallingAppTrusted(true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testRedirectMethods() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
// User clicks a link.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
// Redirects to youtube with javascript simulated link click.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Redirects to youtube with client redirect ('window.location =' or meta refresh).
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.CLIENT_REDIRECT,
false,
false,
0,
1,
false,
true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Redirects to youtube with server redirect.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Redirects to youtube with form submission.
redirectHandler.updateNewUrlLoading(
PageTransition.FORM_SUBMIT, false, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Redirects to youtube through history API.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FORWARD_BACK, false, false, 0, 1, false, true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testExcludeBackAndForward() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
// User clicks a link.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
// User clicks back button.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FORWARD_BACK,
false,
false,
1,
1,
false,
false);
// Site redirects to youtube.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.CLIENT_REDIRECT,
false,
false,
1,
1,
false,
true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testLocationReplace() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
// User types a URL.
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, true, 0, 0, false, false);
// User clicks a link using location.replace().
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.CLIENT_REDIRECT,
false,
true,
SystemClock.elapsedRealtime() + 1,
1,
false,
true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIntentFromChrome() throws Exception {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
RedirectHandler redirectHandler = RedirectHandler.create();
Intent fooIntent = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
// Set Chrome AppId for the Intent.
fooIntent.putExtra(Browser.EXTRA_APPLICATION_ID, SELF_PACKAGE_NAME);
redirectHandler.updateIntent(
fooIntent, !IS_CUSTOM_TAB_INTENT, !SEND_TO_EXTERNAL_APPS, !INTENT_STARTED_TASK);
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FROM_API, false, false, 0, 0, false, false);
checkUrl(YOUTUBE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// Redirects to URL with new handlers.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK | PageTransition.FROM_API, true, false, 0, 0, false, false);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
// User clicks link.
redirectHandler.updateNewUrlLoading(
PageTransition.LINK,
false,
true,
SystemClock.elapsedRealtime() + 1,
2,
false,
true);
checkUrl(YOUTUBE_MOBILE_URL, redirectHandler)
.expecting(
OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(
2, redirectHandler.getLastCommittedEntryIndexBeforeStartingNavigation());
}
private void doTestSubframeIntentTargetsSelf(boolean targetsPackage) {
mUrlHandler.mResolveInfoContainsSelf = true;
if (!targetsPackage) {
mDelegate.setWillResolveToDisambiguationDialog(true);
}
String url =
"intent://www.example.com/#Intent;scheme=https;"
+ "action=android.intent.action.VIEW;package="
+ SELF_PACKAGE_NAME
+ ";S.browser_fallback_url=https://bad.com;end";
RedirectHandler redirectHandler = RedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.AUTO_SUBFRAME, false, true, 0, 0, false, true);
checkUrl(url, redirectHandler)
.withIsMainFrame(false)
.withHasUserGesture(true)
.withPageTransition(PageTransition.AUTO_SUBFRAME)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertEquals("https://www.example.com/", mUrlHandler.mNewUrlAfterClobbering);
}
@Test
@SmallTest
public void testSubframeIntentTargetsSelf_Package() {
doTestSubframeIntentTargetsSelf(true);
}
@Test
@SmallTest
public void testSubframeIntentTargetsSelf_Chooser() {
doTestSubframeIntentTargetsSelf(false);
}
@Test
@SmallTest
public void testBlockHiddenCrossFrameReNavigation() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL, redirectHandlerForLinkClick())
.withIsInitialNavigationInFrame(false)
.withIsHiddenCrossFrame(true)
.expecting(OverrideUrlLoadingResultType.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
// Tests googlechrome:// URLs.
public void testSelfSchemeUrl() {
mUrlHandler.mResolveInfoContainsSelf = true;
checkUrl(
SELF_SCHEME
+ ExternalNavigationHandler.SELF_SCHEME_NAVIGATE_PREFIX
+ "https://www.example.com/",
redirectHandlerForLinkClick())
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB, IGNORE);
Assert.assertEquals("https://www.example.com/", mUrlHandler.mNewUrlAfterClobbering);
}
private static List<ResolveInfo> makeResolveInfos(ResolveInfo... infos) {
return Arrays.asList(infos);
}
private static ResolveInfo newResolveInfo(String packageName) {
ActivityInfo ai = new ActivityInfo();
ai.packageName = packageName;
ai.name = "Name: " + packageName;
ai.exported = true;
ResolveInfo ri = new ResolveInfo();
ri.activityInfo = ai;
return ri;
}
private static ResolveInfo newSpecializedResolveInfo(
String packageName, IntentActivity activity) {
ResolveInfo info = newResolveInfo(packageName);
info.filter = new IntentFilter(Intent.ACTION_VIEW);
info.filter.addDataAuthority(activity.mUrlPrefix, null);
info.activityInfo.exported = activity.isExported();
return info;
}
private static class IntentActivity {
private String mUrlPrefix;
private String mPackageName;
private boolean mIsExported;
private boolean mIsNotSpecialized;
public IntentActivity(String urlPrefix, String packageName) {
this(urlPrefix, packageName, true);
}
public IntentActivity(String urlPrefix, String packageName, boolean isExported) {
mUrlPrefix = urlPrefix;
mPackageName = packageName;
mIsExported = isExported;
}
public String urlPrefix() {
return mUrlPrefix;
}
public String packageName() {
return mPackageName;
}
public boolean isExported() {
return mIsExported;
}
public void setIsNotSpecialized(boolean isNotSpecialized) {
mIsNotSpecialized = isNotSpecialized;
}
public boolean isSpecialized() {
if (mIsNotSpecialized) return false;
// Specialized if URL prefix is more than just a scheme.
return Pattern.compile("[^:/]+://.+").matcher(mUrlPrefix).matches();
}
}
private class ExternalNavigationHandlerForTesting extends ExternalNavigationHandler {
public String defaultSmsPackageName;
public GURL mLastCommittedUrl;
public boolean mIsSerpReferrer;
public boolean mShouldRequestFileAccess;
public String mNewUrlAfterClobbering;
public String mReferrerUrlForClobbering;
public boolean mRequestFilePermissionsCalled;
public Intent mStartActivityInIncognitoIntent;
public boolean mStartIncognitoIntentCalled;
public boolean mCanShowIncognitoDialog;
public boolean mResolveInfoContainsSelf;
public Intent mStartActivityIntent;
public boolean mRequiresIntentChooser;
private boolean mSendIntentsForReal;
public boolean mExpectingMessage;
public Callback<AsyncActionTakenParams> mAsyncActionCallback;
public ExternalNavigationHandlerForTesting(ExternalNavigationDelegate delegate) {
super(delegate);
}
@Override
protected boolean isValidWebApk(String packageName) {
return packageName.startsWith(WEBAPK_PACKAGE_PREFIX)
&& !packageName.equals(INVALID_WEBAPK_PACKAGE_NAME);
}
@Override
protected boolean canLaunchIncognitoIntent(Intent intent, Context context) {
mStartActivityInIncognitoIntent = intent;
mStartIncognitoIntentCalled = true;
return mCanShowIncognitoDialog;
}
@Override
protected IncognitoDialogDelegate showLeavingIncognitoDialog(
Context context, ExternalNavigationParams params, Intent intent, GURL fallbackUrl) {
if (context instanceof TestContext) return mIncognitoDialogDelegateMock;
return super.showLeavingIncognitoDialog(context, params, intent, fallbackUrl);
}
@Override
protected String getDefaultSmsPackageNameFromSystem() {
return defaultSmsPackageName;
}
@Override
protected GURL getLastCommittedUrl() {
return mLastCommittedUrl;
}
@Override
protected boolean isSerpReferrer() {
return mIsSerpReferrer;
}
@Override
protected boolean shouldRequestFileAccess(GURL url, String permissionNeeded) {
return mShouldRequestFileAccess;
}
@Override
protected void requestFilePermissions(
ExternalNavigationParams params, String permissionNeeded) {
mRequestFilePermissionsCalled = true;
}
@Override
public boolean isYoutubePairingCode(GURL url) {
return super.isYoutubePairingCode(url);
}
@Override
protected boolean resolveInfoContainsSelf(List<ResolveInfo> resolveInfos) {
return mResolveInfoContainsSelf;
}
@Override
protected OverrideUrlLoadingResult startActivity(
Intent intent,
ExternalNavigationParams params,
boolean requiresIntentChooser,
QueryIntentActivitiesSupplier resolvingInfos,
ResolveActivitySupplier resolveActivity,
GURL browserFallbackUrl,
GURL intentDataUrl) {
mStartActivityIntent = intent;
mRequiresIntentChooser = requiresIntentChooser;
if (mSendIntentsForReal) {
return super.startActivity(
intent,
params,
requiresIntentChooser,
resolvingInfos,
resolveActivity,
browserFallbackUrl,
intentDataUrl);
}
return OverrideUrlLoadingResult.forExternalIntent();
}
public void sendIntentsForReal() {
mSendIntentsForReal = true;
}
public void reset() {
mStartActivityIntent = null;
mStartIncognitoIntentCalled = false;
}
@Override
protected OverrideUrlLoadingResult maybeAskToLaunchApp(
boolean isExternalProtocol,
Intent targetIntent,
QueryIntentActivitiesSupplier resolvingInfos,
ResolveActivitySupplier resolveActivity,
GURL browserFallbackUrl,
ExternalNavigationParams params) {
if (!browserFallbackUrl.isEmpty() || !isExternalProtocol) {
return super.maybeAskToLaunchApp(
isExternalProtocol,
targetIntent,
resolvingInfos,
resolveActivity,
browserFallbackUrl,
params);
}
Assert.assertTrue(mExpectingMessage);
return OverrideUrlLoadingResult.forAsyncAction();
}
public IncognitoDialogDelegate spyIncognitoDialogDelegate() {
mIncognitoDialogDelegate = Mockito.spy(mIncognitoDialogDelegate);
return mIncognitoDialogDelegate;
}
}
private static class TestExternalNavigationDelegate implements ExternalNavigationDelegate {
private WindowAndroid mWindowAndroid;
public List<ResolveInfo> queryIntentActivities(Intent intent) {
List<ResolveInfo> list = new ArrayList<>();
String dataString = intent.getDataString();
if (intent.getScheme() != null) {
if (dataString.startsWith("http://")
|| dataString.startsWith("https://")
|| intent.getScheme().equals(SELF_SCHEME)) {
list.add(newResolveInfo(SELF_PACKAGE_NAME));
}
for (IntentActivity intentActivity : mIntentActivities) {
if (dataString.startsWith(intentActivity.urlPrefix())) {
list.add(
newSpecializedResolveInfo(
intentActivity.packageName(), intentActivity));
}
}
String schemeString = intent.getScheme();
boolean isMarketScheme = schemeString != null && schemeString.startsWith("market");
if (mCanResolveActivityForMarket && isMarketScheme) {
list.add(newResolveInfo("market"));
return list;
}
if (mCanResolveActivityForExternalSchemes && !isMarketScheme) {
list.add(newResolveInfo(intent.getData().getScheme()));
}
} else if (mCanResolveActivityForExternalSchemes) {
// Scheme-less intents (eg. Action-based intents like opening Settings).
list.add(newResolveInfo("package"));
}
if (mResolvesToOtherBrowser) {
list.add(newResolveInfo(OTHER_BROWSER_PACKAGE));
}
return list;
}
public ResolveInfo resolveActivity(Intent intent) {
if (mWillResolveToDisambiguationDialog) {
return newResolveInfo("android.disambiguation.dialog");
}
if (mResolvesToOtherBrowser) {
return newResolveInfo(OTHER_BROWSER_PACKAGE);
}
List<ResolveInfo> list = queryIntentActivities(intent);
return list.size() > 0 ? list.get(0) : null;
}
@Override
public Context getContext() {
return mContext;
}
@Override
public boolean willAppHandleIntent(Intent intent) {
String chromePackageName = ContextUtils.getApplicationContext().getPackageName();
if (chromePackageName.equals(intent.getPackage())
|| (intent.getComponent() != null
&& chromePackageName.equals(intent.getComponent().getPackageName()))) {
return true;
}
List<ResolveInfo> resolveInfos = queryIntentActivities(intent);
return resolveInfos.size() == 1
&& resolveInfos.get(0).activityInfo.packageName.contains("chrome");
}
@Override
public boolean shouldDisableExternalIntentRequestsForUrl(GURL url) {
return mShouldDisableExternalIntentRequests;
}
@Override
public boolean canLoadUrlInCurrentTab() {
return mCanLoadUrlInTab;
}
@Override
public void closeTab() {}
@Override
public boolean isIncognito() {
return false;
}
@Override
public boolean hasCustomLeavingIncognitoDialog() {
return mShouldPresentLeavingIncognitoDialog;
}
@Override
public void presentLeavingIncognitoModalDialog(Callback<Boolean> onUserDecision) {
incognitoDialogUserDecisionCallback = onUserDecision;
}
@Override
public void maybeSetWindowId(Intent intent) {}
@Override
public void maybeSetPendingReferrer(Intent intent, GURL referrerUrl) {
// This is used in a test to check that ExternalNavigationHandler correctly passes
// this data to the delegate when the referrer URL is non-null.
intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(referrerUrl.getSpec()));
}
@Override
public void maybeSetRequestMetadata(
Intent intent, boolean hasUserGesture, boolean isRendererInitiated) {
maybeSetRequestMetadataCalled = true;
}
@Override
public void maybeSetPendingIncognitoUrl(Intent intent) {}
@Override
public boolean isApplicationInForeground() {
return mIsChromeAppInForeground;
}
@Override
public WindowAndroid getWindowAndroid() {
return mWindowAndroid;
}
@Override
public WebContents getWebContents() {
return null;
}
@Override
public boolean hasValidTab() {
return false;
}
@Override
public boolean canCloseTabOnIncognitoIntentLaunch() {
return false;
}
@Override
public boolean isForTrustedCallingApp(Supplier<List<ResolveInfo>> resolveInfoSupplier) {
return mIsCallingAppTrusted;
}
@Override
public boolean shouldLaunchWebApksOnInitialIntent() {
return mShouldLaunchWebApksOnInitialIntent;
}
@Override
public void setPackageForTrustedCallingApp(Intent intent) {
assert mIsCallingAppTrusted;
if (mTargetPackageName != null) {
intent.setPackage(mTargetPackageName);
}
}
@Override
public boolean shouldAvoidDisambiguationDialog(GURL intentDataUrl) {
return mShouldAvoidDisambiguationDialog;
}
@Override
public boolean shouldEmbedderInitiatedNavigationsStayInBrowser() {
return mShouldEmbedderInitiatedNavigationsStayInBrowser;
}
@Override
public String getSelfScheme() {
return SELF_SCHEME;
}
@Override
public boolean shouldDisableAllExternalIntents() {
return mShouldDisableAllExternalIntents;
}
@Override
public boolean shouldReturnAsActivityResult(GURL url) {
return mShouldReturnAsActivityResult;
}
@Override
public void returnAsActivityResult(GURL url) {}
public void reset() {
startIncognitoIntentCalled = false;
}
public void setContext(Context context) {
mContext = context;
}
public void add(IntentActivity handler) {
mIntentActivities.add(handler);
}
public void setCanResolveActivityForExternalSchemes(boolean value) {
mCanResolveActivityForExternalSchemes = value;
}
public void setCanResolveActivityForMarket(boolean value) {
mCanResolveActivityForMarket = value;
}
public void setIsChromeAppInForeground(boolean value) {
mIsChromeAppInForeground = value;
}
public void setReferrerWebappPackageName(String webappPackageName) {
mReferrerWebappPackageName = webappPackageName;
}
public String getReferrerWebappPackageName() {
return mReferrerWebappPackageName;
}
public void setIsCallingAppTrusted(boolean trusted) {
mIsCallingAppTrusted = trusted;
}
public void setDisableExternalIntentRequests(boolean disable) {
mShouldDisableExternalIntentRequests = disable;
}
public void setCanLoadUrlInTab(boolean value) {
mCanLoadUrlInTab = value;
}
public void setShouldPresentLeavingIncognitoDialog(boolean value) {
mShouldPresentLeavingIncognitoDialog = value;
}
public void setShouldLaunchWebApksOnInitialIntent(boolean value) {
mShouldLaunchWebApksOnInitialIntent = value;
}
public void setTargetPackageName(String targetPackageName) {
mTargetPackageName = targetPackageName;
}
public void setShouldAvoidDisambiguationDialog(boolean value) {
mShouldAvoidDisambiguationDialog = value;
}
public void setWillResolveToDisambiguationDialog(boolean value) {
mWillResolveToDisambiguationDialog = value;
}
public void setShouldEmbedderInitiatedNavigationsStayInBrowser(boolean value) {
mShouldEmbedderInitiatedNavigationsStayInBrowser = value;
}
public void setResolvesToOtherBrowser(boolean value) {
mResolvesToOtherBrowser = value;
}
public void setWindowAndroid(WindowAndroid windowAndroid) {
mWindowAndroid = windowAndroid;
}
public void setShouldDisableAllExternalIntents(boolean disable) {
mShouldDisableAllExternalIntents = disable;
}
public void setShouldReturnAsActivityResult(boolean returnResult) {
mShouldReturnAsActivityResult = returnResult;
}
public boolean startIncognitoIntentCalled;
public boolean maybeSetRequestMetadataCalled;
public Callback<Boolean> incognitoDialogUserDecisionCallback;
private String mReferrerWebappPackageName;
private ArrayList<IntentActivity> mIntentActivities = new ArrayList<IntentActivity>();
private boolean mCanResolveActivityForExternalSchemes = true;
private boolean mCanResolveActivityForMarket = true;
public boolean mIsChromeAppInForeground = true;
private boolean mIsCallingAppTrusted;
private boolean mShouldDisableExternalIntentRequests;
private boolean mCanLoadUrlInTab;
private boolean mShouldPresentLeavingIncognitoDialog;
private boolean mShouldLaunchWebApksOnInitialIntent;
private String mTargetPackageName;
private boolean mShouldAvoidDisambiguationDialog;
private boolean mWillResolveToDisambiguationDialog;
private Context mContext;
private boolean mShouldEmbedderInitiatedNavigationsStayInBrowser = true;
private boolean mResolvesToOtherBrowser;
private boolean mShouldDisableAllExternalIntents;
private boolean mShouldReturnAsActivityResult;
}
private void checkIntentSanity(Intent intent, String name) {
Assert.assertTrue(
"The invoked " + name + " doesn't have the BROWSABLE category set\n",
intent.hasCategory(Intent.CATEGORY_BROWSABLE));
Assert.assertNull(
"The invoked " + name + " should not have a Component set\n",
intent.getComponent());
}
private ExternalNavigationTestParams checkUrl(String url, RedirectHandler handler) {
return new ExternalNavigationTestParams(url, handler);
}
private class ExternalNavigationTestParams {
private final String mUrl;
private String mReferrerUrl;
private boolean mIsIncognito;
private int mPageTransition = PageTransition.LINK;
private boolean mIsRedirect;
private boolean mChromeAppInForegroundRequired = true;
private boolean mIsBackgroundTabNavigation;
private boolean mIntentLaunchesAllowedInBackgroundTabs;
private boolean mHasUserGesture;
private RedirectHandler mRedirectHandler;
private boolean mIsRendererInitiated = true;
private boolean mIsMainFrame = true;
private boolean mIsInitialNavigationInFrame;
private boolean mIsHiddenCrossFrame;
private long mNavigationId;
private ExternalNavigationTestParams(String url, RedirectHandler handler) {
mUrl = url;
mRedirectHandler = handler;
}
public ExternalNavigationTestParams withReferrer(String referrerUrl) {
mReferrerUrl = referrerUrl;
return this;
}
public ExternalNavigationTestParams withIsIncognito(boolean isIncognito) {
mIsIncognito = isIncognito;
return this;
}
public ExternalNavigationTestParams withPageTransition(int pageTransition) {
mPageTransition = pageTransition;
return this;
}
public ExternalNavigationTestParams withIsRedirect(boolean isRedirect) {
mIsRedirect = isRedirect;
return this;
}
public ExternalNavigationTestParams withHasUserGesture(boolean hasGesture) {
mHasUserGesture = hasGesture;
return this;
}
public ExternalNavigationTestParams withChromeAppInForegroundRequired(
boolean foregroundRequired) {
mChromeAppInForegroundRequired = foregroundRequired;
return this;
}
public ExternalNavigationTestParams withIsBackgroundTabNavigation(
boolean isBackgroundTabNavigation) {
mIsBackgroundTabNavigation = isBackgroundTabNavigation;
return this;
}
public ExternalNavigationTestParams withAllowIntentLaunchesInBackgroundTabs(
boolean allowIntentLaunchesInBackgroundTabs) {
mIntentLaunchesAllowedInBackgroundTabs = allowIntentLaunchesInBackgroundTabs;
return this;
}
public ExternalNavigationTestParams withIsRendererInitiated(boolean isRendererInitiated) {
mIsRendererInitiated = isRendererInitiated;
return this;
}
public ExternalNavigationTestParams withIsMainFrame(boolean isMainFrame) {
mIsMainFrame = isMainFrame;
return this;
}
public ExternalNavigationTestParams withIsInitialNavigationInFrame(
boolean isInitialNavigationInFrame) {
mIsInitialNavigationInFrame = isInitialNavigationInFrame;
return this;
}
public ExternalNavigationTestParams withIsHiddenCrossFrame(boolean isHiddenCrossFrame) {
mIsHiddenCrossFrame = isHiddenCrossFrame;
return this;
}
public ExternalNavigationTestParams withNavigationId(long navigationId) {
mNavigationId = navigationId;
return this;
}
public void expecting(
@OverrideUrlLoadingResultType int expectedOverrideResult, int otherExpectation) {
boolean expectStartIncognito = (otherExpectation & START_INCOGNITO) != 0;
boolean expectStartActivity =
(otherExpectation & (START_WEBAPK | START_OTHER_ACTIVITY)) != 0;
boolean expectStartWebApk = (otherExpectation & START_WEBAPK) != 0;
boolean expectStartOtherActivity = (otherExpectation & START_OTHER_ACTIVITY) != 0;
boolean expectStartFile = (otherExpectation & START_FILE) != 0;
boolean expectSaneIntent =
expectStartOtherActivity
&& (otherExpectation & INTENT_SANITIZATION_EXCEPTION) == 0;
mDelegate.reset();
mUrlHandler.reset();
Callback<AsyncActionTakenParams> callback =
new Callback<AsyncActionTakenParams>() {
@Override
public void onResult(AsyncActionTakenParams params) {
if (params.actionType == AsyncActionTakenType.NAVIGATE) {
mUrlHandler.mNewUrlAfterClobbering = params.targetUrl.getSpec();
mUrlHandler.mReferrerUrlForClobbering =
params.externalNavigationParams.getReferrerUrl().getSpec();
}
}
};
ExternalNavigationParams params =
new ExternalNavigationParams.Builder(
new GURL(mUrl),
mIsIncognito,
new GURL(mReferrerUrl),
mPageTransition,
mIsRedirect)
.setApplicationMustBeInForeground(mChromeAppInForegroundRequired)
.setRedirectHandler(mRedirectHandler)
.setIsBackgroundTabNavigation(mIsBackgroundTabNavigation)
.setIntentLaunchesAllowedInBackgroundTabs(
mIntentLaunchesAllowedInBackgroundTabs)
.setIsMainFrame(mIsMainFrame)
.setNativeClientPackageName(mDelegate.getReferrerWebappPackageName())
.setHasUserGesture(mHasUserGesture)
.setIsRendererInitiated(mIsRendererInitiated)
.setAsyncActionTakenCallback(callback)
.setIsInitialNavigationInFrame(mIsInitialNavigationInFrame)
.setIsHiddenCrossFrameNavigation(mIsHiddenCrossFrame)
.setNavigationId(mNavigationId)
.build();
OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
if (result.getResultType() == OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB) {
mUrlHandler.mNewUrlAfterClobbering = result.mTargetUrl.getSpec();
mUrlHandler.mReferrerUrlForClobbering =
result.mExternalNavigationParams.getReferrerUrl().getSpec();
}
if (result.getResultType() == OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION) {
mUrlHandler.mAsyncActionCallback = params.getRequiredAsyncActionTakenCallback();
}
boolean startActivityCalled = false;
boolean startWebApkCalled = false;
Intent startActivityIntent = mUrlHandler.mStartActivityIntent;
if (startActivityIntent != null) {
startActivityCalled = true;
String packageName = startActivityIntent.getPackage();
if (packageName != null) {
startWebApkCalled = packageName.startsWith(WEBAPK_PACKAGE_PREFIX);
}
}
Assert.assertEquals(expectedOverrideResult, result.getResultType());
Assert.assertEquals(expectStartIncognito, mUrlHandler.mStartIncognitoIntentCalled);
Assert.assertEquals(expectStartActivity, startActivityCalled);
Assert.assertEquals(expectStartWebApk, startWebApkCalled);
Assert.assertEquals(expectStartFile, mUrlHandler.mRequestFilePermissionsCalled);
if (startActivityCalled && expectSaneIntent) {
checkIntentSanity(startActivityIntent, "Intent");
if (startActivityIntent.getSelector() != null) {
checkIntentSanity(startActivityIntent.getSelector(), "Intent's selector");
}
}
}
}
private static String getPackageName() {
return ContextUtils.getApplicationContext().getPackageName();
}
private static class TestPackageManager extends MockPackageManager {
private TestExternalNavigationDelegate mDelegate;
public TestPackageManager(TestExternalNavigationDelegate delegate) {
mDelegate = delegate;
}
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
return mDelegate.queryIntentActivities(intent);
}
@Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
Assert.assertTrue((flags & PackageManager.MATCH_DEFAULT_ONLY) > 0);
return mDelegate.resolveActivity(intent);
}
}
private static class TestContext extends ContextWrapper {
private PackageManager mPackageManager;
public TestContext(Context baseContext, TestExternalNavigationDelegate delegate) {
super(baseContext);
mPackageManager = new TestPackageManager(delegate);
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@Override
public String getPackageName() {
return SELF_PACKAGE_NAME;
}
@Override
public void startActivities(Intent[] intents, Bundle options) {
throw new UnsupportedOperationException();
}
@Override
public void startActivity(Intent intent) {}
@Override
public void startActivity(Intent intent, Bundle options) {
throw new UnsupportedOperationException();
}
}
}