blob: ac3c68289185acf96c208f819c1ef3de3d583fd9 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.externalnav;
import android.annotation.SuppressLint;
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.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.test.mock.MockPackageManager;
import androidx.browser.customtabs.CustomTabsIntent;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.DisableIf;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.externalnav.ExternalNavigationHandler.OverrideUrlLoadingResult;
import org.chromium.chrome.browser.instantapps.InstantAppsHandler;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabRedirectHandler;
import org.chromium.chrome.browser.webapps.WebappInfo;
import org.chromium.chrome.browser.webapps.WebappScopePolicy;
import org.chromium.chrome.test.util.browser.webapps.WebappTestHelper;
import org.chromium.content_public.browser.test.NativeLibraryTestRule;
import org.chromium.ui.base.PageTransition;
import org.chromium.webapk.lib.common.WebApkConstants;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
/**
* Instrumentation tests for {@link ExternalNavigationHandler}.
*/
@RunWith(BaseJUnit4ClassRunner.class)
@DisableIf.Build(message = "Flaky on K - see https://crbug.com/851444",
sdk_is_less_than = Build.VERSION_CODES.LOLLIPOP)
public class ExternalNavigationHandlerTest {
@Rule
public final NativeLibraryTestRule mNativeLibraryTestRule = new NativeLibraryTestRule();
// 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_WEBAPP_CCT = 0x8;
private static final int START_OTHER_ACTIVITY = 0x10;
private static final int INTENT_SANITIZATION_EXCEPTION = 0x20;
private static final int PROXY_FOR_INSTANT_APPS = 0x40;
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_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 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_NAME = "org.chromium.webapk.template";
private static final String[] SUPERVISOR_START_ACTIONS = {
"com.google.android.instantapps.START", "com.google.android.instantapps.nmr1.INSTALL",
"com.google.android.instantapps.nmr1.VIEW"};
private Context mContext;
private final TestExternalNavigationDelegate mDelegate;
private ExternalNavigationHandler mUrlHandler;
public ExternalNavigationHandlerTest() {
mDelegate = new TestExternalNavigationDelegate();
mUrlHandler = new ExternalNavigationHandler(mDelegate);
}
@Before
public void setUp() {
RecordHistogram.setDisabledForTests(true);
mContext = new TestContext(InstrumentationRegistry.getTargetContext(), mDelegate);
ContextUtils.initApplicationContextForTests(mContext);
HashMap<String, Boolean> features = new HashMap<String, Boolean>();
features.put(ChromeFeatureList.CCT_EXTERNAL_LINK_HANDLING, true);
features.put(ChromeFeatureList.INTENT_BLOCK_EXTERNAL_FORM_REDIRECT_NO_GESTURE, true);
ChromeFeatureList.setTestFeatures(features);
mNativeLibraryTestRule.loadNativeLibraryNoBrowserProcess();
}
@After
public void tearDown() {
RecordHistogram.setDisabledForTests(false);
}
@Test
@SmallTest
public void testStartActivityToTrustedPackageWithoutUserGesture() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler handler = TabRedirectHandler.create();
handler.updateNewUrlLoading(PageTransition.CLIENT_REDIRECT, false, false, 0, 0);
checkUrl(YOUTUBE_URL)
.withRedirectHandler(handler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
mDelegate.setIsCallingAppTrusted(true);
checkUrl(YOUTUBE_URL)
.withRedirectHandler(handler)
.expecting(OverrideUrlLoadingResult.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)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.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)
.withReferrer("chrome://about")
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("tel:012345678")
.withReferrer("chrome://about")
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testForwardBackNavigation() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// http://crbug.com/164194. We shouldn't show the intent picker on
// forwards or backwards navigations.
checkUrl(YOUTUBE_URL)
.withPageTransition(PageTransition.LINK | PageTransition.FORWARD_BACK)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// 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")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
checkUrl("http://youtube.com://")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// If the page matches the referrer, then continue loading in Chrome.
checkUrl("http://youtube.com://")
.withReferrer(YOUTUBE_URL)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// If the page does not match the referrer, then prompt an intent.
checkUrl("http://youtube.com://")
.withReferrer("http://google.com")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// 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://")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit_NoUserGesture() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
// If the redirect is not associated with a user gesture, then continue loading in Chrome.
checkUrl("market://1234")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("http://youtube.com://")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testRedirectFromFormSubmit_NoUserGesture_OnIntentRedirectChain() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = new TabRedirectHandler() {
@Override
public boolean isOnEffectiveIntentRedirectChain() {
return 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")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
checkUrl("http://youtube.com://")
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(false)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIgnore() {
// Ensure about: URLs are not broadcast for external navigation.
checkUrl("about:test").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("about:test")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Ensure content: URLs are not broadcast for external navigation.
checkUrl("content:test").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("content:test")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Ensure chrome: URLs are not broadcast for external navigation.
checkUrl("chrome://history").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("chrome://history")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Ensure chrome-native: URLs are not broadcast for external navigation.
checkUrl("chrome-native://newtab").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl("chrome-native://newtab")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.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)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
checkUrl(YOUTUBE_URL)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// http://crbug.com/143118 - Don't show the picker for directly typed URLs, unless
// the URL results in a redirect.
checkUrl(YOUTUBE_URL)
.withPageTransition(PageTransition.TYPED)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// http://crbug.com/162106 - Don't show the picker on reload.
checkUrl(YOUTUBE_URL)
.withPageTransition(PageTransition.RELOAD)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testWtai() {
// Start the telephone application with the given number.
checkUrl("wtai://wp/mc;0123456789")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY | INTENT_SANITIZATION_EXCEPTION);
// These two cases are currently unimplemented.
checkUrl("wtai://wp/sd;0123456789")
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
checkUrl("wtai://wp/ap;0123456789")
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
// Ignore other WTAI urls.
checkUrl("wtai://wp/invalid")
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE,
IGNORE | INTENT_SANITIZATION_EXCEPTION);
}
@Test
@SmallTest
public void testRedirectToMarketWithReferrer() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_APP_NOT_INSTALLED_WITH_MARKET_REFERRER)
.withReferrer(KEEP_URL)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Uri uri = mDelegate.startActivityIntent.getData();
Assert.assertEquals("market", uri.getScheme());
Assert.assertEquals(Uri.decode(ENCODED_MARKET_REFERRER), uri.getQueryParameter("referrer"));
Assert.assertEquals(Uri.parse(KEEP_URL),
mDelegate.startActivityIntent.getParcelableExtra(Intent.EXTRA_REFERRER));
}
@Test
@SmallTest
public void testRedirectToMarketWithoutReferrer() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_APP_NOT_INSTALLED_DEFAULT_MARKET_REFERRER)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Uri uri = mDelegate.startActivityIntent.getData();
Assert.assertEquals("market", uri.getScheme());
Assert.assertEquals(getPackageName(), uri.getQueryParameter("referrer"));
}
@Test
@SmallTest
public void testExternalUri() {
checkUrl("tel:012345678")
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testTypedRedirectToExternalProtocol() {
// http://crbug.com/169549
checkUrl("market://1234")
.withPageTransition(PageTransition.TYPED)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// http://crbug.com/709217
checkUrl("market://1234")
.withPageTransition(PageTransition.FROM_ADDRESS_BAR)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// http://crbug.com/143118
checkUrl("market://1234")
.withPageTransition(PageTransition.TYPED)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@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)
.withPageTransition(transitionTypeIncomingIntent)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// http://crbug.com/170925
checkUrl(YOUTUBE_URL)
.withPageTransition(transitionTypeIncomingIntent)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testIntentScheme() {
String url = "intent:wtai://wp/#Intent;action=android.settings.SETTINGS;"
+ "component=package/class;end";
String urlWithSel = "intent:wtai://wp/#Intent;SEL;action=android.settings.SETTINGS;"
+ "component=package/class;end";
checkUrl(url).expecting(
OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, START_OTHER_ACTIVITY);
// http://crbug.com/370399
checkUrl(urlWithSel)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testYouTubePairingCode() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
int transitionTypeIncomingIntent = PageTransition.LINK
| PageTransition.FROM_API;
final String[] goodUrls = {"http://m.youtube.com/watch?v=1234&pairingCode=5678",
"youtube.com?pairingCode=xyz", "youtube.com/tv?pairingCode=xyz",
"youtube.com/watch?v=1234&version=3&autohide=1&pairingCode=xyz",
"youtube.com/watch?v=1234&pairingCode=xyz&version=3&autohide=1"};
final String[] badUrls = {"youtube.com.foo.com/tv?pairingCode=xyz",
"youtube.com.foo.com?pairingCode=xyz",
"youtube.com/watch?v=tEsT&version=3&autohide=1&pairingCode=",
"youtube.com&pairingCode=xyz",
"youtube.com/watch?v=tEsT?version=3&pairingCode=&autohide=1"};
// Make sure we don't override when faced with valid pairing code URLs.
for (String url : goodUrls) {
// http://crbug/386600 - it makes no sense to switch activities for pairing code URLs.
checkUrl(url).withIsRedirect(true).expecting(
OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl(url)
.withPageTransition(transitionTypeIncomingIntent)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
// The pairing code URL regex shouldn't cause NO_OVERRIDE on invalid URLs.
for (String url : badUrls) {
checkUrl(url).withIsRedirect(true).expecting(
OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, START_OTHER_ACTIVITY);
checkUrl(url)
.withPageTransition(transitionTypeIncomingIntent)
.withIsRedirect(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
}
@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));
TabRedirectHandler redirectHandler = TabRedirectHandler.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);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Do not ignore if a new intent has any new resolver.
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// Do not ignore if a new intent cannot be handled by Chrome.
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl("intent://myownurl")
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void
testInitialIntentHeadingToChrome() throws URISyntaxException {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.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);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Do not ignore if the URI has an external protocol.
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl("market://1234")
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void
testIntentForCustomTab() throws URISyntaxException {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.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.putExtra(CustomTabsIntent.EXTRA_SESSION, "");
barIntent.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(barIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
checkUrl(YOUTUBE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.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.putExtra(CustomTabsIntent.EXTRA_SESSION, "");
fooIntent.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl(YOUTUBE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.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.putExtra(CustomTabsIntent.EXTRA_SESSION, "");
extraIntent2.putExtra(
CustomTabIntentDataProvider.EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER, true);
extraIntent2.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(extraIntent2);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
checkUrl(YOUTUBE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Intent extraIntent3 = Intent.parseUri(YOUTUBE_URL, Intent.URI_INTENT_SCHEME);
extraIntent3.putExtra(CustomTabsIntent.EXTRA_SESSION, "");
extraIntent3.putExtra(
CustomTabIntentDataProvider.EXTRA_SEND_TO_EXTERNAL_DEFAULT_HANDLER, true);
extraIntent3.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(extraIntent3);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
checkUrl(YOUTUBE_URL)
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
// External intent for a user-initiated navigation should always be allowed.
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
// Simulate a real user navigation.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true,
SystemClock.elapsedRealtime() + 1, 0);
checkUrl(YOUTUBE_URL)
.withPageTransition(PageTransition.LINK)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void
testInstantAppsIntent_customTabRedirect() throws Exception {
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
int transTypeLinkFromIntent = PageTransition.LINK | PageTransition.FROM_API;
// In Custom Tabs, if the first url is a redirect, don't allow it to intent out, unless
// the redirect is going to Instant Apps.
Intent fooIntent = Intent.parseUri("http://foo.com/", Intent.URI_INTENT_SCHEME);
fooIntent.putExtra(CustomTabsIntent.EXTRA_SESSION, "");
fooIntent.putExtra(CustomTabsIntent.EXTRA_ENABLE_INSTANT_APPS, true);
fooIntent.setPackage(mContext.getPackageName());
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
mDelegate.setCanHandleWithInstantApp(true);
checkUrl("http://instantappenabled.com")
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, IGNORE);
}
@Test
@SmallTest
public void testInstantAppsIntent_incomingIntentRedirect() throws Exception {
int transTypeLinkFromIntent = PageTransition.LINK
| PageTransition.FROM_API;
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
Intent fooIntent = Intent.parseUri("http://instantappenabled.com",
Intent.URI_INTENT_SCHEME);
redirectHandler.updateIntent(fooIntent);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, false, false, 0, 0);
redirectHandler.updateNewUrlLoading(transTypeLinkFromIntent, true, false, 0, 0);
mDelegate.setCanHandleWithInstantApp(true);
checkUrl("http://goo.gl/1234")
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, IGNORE);
// URL that cannot be handled with instant apps should stay in Chrome.
mDelegate.setCanHandleWithInstantApp(false);
checkUrl("http://goo.gl/1234")
.withPageTransition(transTypeLinkFromIntent)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testInstantAppsIntent_handleNavigation() {
mDelegate.setCanHandleWithInstantApp(false);
checkUrl("http://maybeinstantapp.com")
.withPageTransition(PageTransition.LINK)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
mDelegate.setCanHandleWithInstantApp(true);
checkUrl("http://maybeinstantapp.com")
.withPageTransition(PageTransition.LINK)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, IGNORE);
}
@Test
@SmallTest
public void
testInstantAppsIntent_serpReferrer() {
String intentUrl = "intent://buzzfeed.com/tasty#Intent;scheme=http;"
+ "package=com.google.android.instantapps.supervisor;"
+ "action=com.google.android.instantapps.START;"
+ "S.com.google.android.instantapps.FALLBACK_PACKAGE="
+ "com.android.chrome;S.com.google.android.instantapps.INSTANT_APP_PACKAGE="
+ "com.yelp.android;S.android.intent.extra.REFERRER_NAME="
+ "https%3A%2F%2Fwww.google.com;end";
mDelegate.setIsSerpReferrer(true);
checkUrl(intentUrl)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY | PROXY_FOR_INSTANT_APPS);
Assert.assertTrue(mDelegate.startActivityIntent.hasExtra(
InstantAppsHandler.IS_GOOGLE_SEARCH_REFERRER));
// Check that we block all instant app intent:// URLs not from SERP
mDelegate.setIsSerpReferrer(false);
checkUrl(intentUrl)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Check that IS_GOOGLE_SEARCH_REFERRER param is stripped on non-supervisor intents.
mDelegate.setIsSerpReferrer(true);
String nonSupervisor = "intent://buzzfeed.com/tasty#Intent;scheme=http;"
+ "package=com.imdb;action=com.google.VIEW;"
+ "S.com.google.android.gms.instantapps.IS_GOOGLE_SEARCH_REFERRER="
+ "true;S.android.intent.extra.REFERRER_NAME="
+ "https%3A%2F%2Fwww.google.com;end";
checkUrl(nonSupervisor)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertFalse(mDelegate.startActivityIntent.hasExtra(
InstantAppsHandler.IS_GOOGLE_SEARCH_REFERRER));
// Check that Supervisor is detected by action even without package
for (String action : SUPERVISOR_START_ACTIONS) {
String intentWithoutPackage = "intent://buzzfeed.com/tasty#Intent;scheme=http;"
+ "action=" + action + ";"
+ "S.com.google.android.instantapps.FALLBACK_PACKAGE="
+ "com.android.chrome;S.com.google.android.instantapps.INSTANT_APP_PACKAGE="
+ "com.yelp.android;S.android.intent.extra.REFERRER_NAME="
+ "https%3A%2F%2Fwww.google.com;end";
mDelegate.setIsSerpReferrer(false);
checkUrl(intentWithoutPackage)
.expecting(OverrideUrlLoadingResult.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)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Intent invokedIntent = mDelegate.startActivityIntent;
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(mDelegate.getNewUrlAfterClobbering());
Assert.assertNull(mDelegate.getReferrerUrlForClobbering());
}
@Test
@SmallTest
public void
testFallbackUrl_IntentResolutionSucceedsInIncognito() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
// Expect that the user is prompted to leave incognito mode.
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withIsIncognito(true)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO | START_OTHER_ACTIVITY);
Assert.assertNull(mDelegate.getNewUrlAfterClobbering());
Assert.assertNull(mDelegate.getReferrerUrlForClobbering());
}
@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)
.withIsWebApk(true));
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.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)
.withIsWebApk(true));
mDelegate.add(new IntentActivity(IMDB_WEBPAGE_FOR_TOM_HANKS, TEXT_APP_1_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
Assert.assertNull(mDelegate.startActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mDelegate.getNewUrlAfterClobbering());
Assert.assertEquals(
SEARCH_RESULT_URL_FOR_TOM_HANKS, mDelegate.getReferrerUrlForClobbering());
}
@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)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
Assert.assertNull(mDelegate.startActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mDelegate.getNewUrlAfterClobbering());
Assert.assertEquals(
SEARCH_RESULT_URL_FOR_TOM_HANKS, mDelegate.getReferrerUrlForClobbering());
}
@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)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals("market://details?id=com.imdb.mobile&referrer=mypage",
mDelegate.startActivityIntent.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)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals("market://details?id=com.imdb.mobile&referrer=" + getPackageName(),
mDelegate.startActivityIntent.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)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
}
@Test
@SmallTest
public void testFallbackUrl_RedirectToIntentToMarket() {
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
checkUrl("http://goo.gl/abcdefg")
.withPageTransition(PageTransition.TYPED)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0);
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)
.withPageTransition(PageTransition.LINK)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals("market://details?id=com.imdb.mobile&referrer=mypage",
mDelegate.startActivityIntent.getDataString());
}
@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)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
Assert.assertNull(mDelegate.startActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mDelegate.getNewUrlAfterClobbering());
Assert.assertEquals(
SEARCH_RESULT_URL_FOR_TOM_HANKS, mDelegate.getReferrerUrlForClobbering());
}
@Test
@SmallTest
public void testFallbackUrl_FallbackShouldNotWarnOnIncognito() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
Assert.assertNull(mDelegate.startActivityIntent);
Assert.assertEquals(IMDB_WEBPAGE_FOR_TOM_HANKS, mDelegate.getNewUrlAfterClobbering());
Assert.assertEquals(
SEARCH_RESULT_URL_FOR_TOM_HANKS, mDelegate.getReferrerUrlForClobbering());
}
@Test
@SmallTest
public void testFallbackUrl_IgnoreJavascriptFallbackUrl() {
// IMDB app isn't installed.
mDelegate.setCanResolveActivityForExternalSchemes(false);
// Will be redirected to market since package is given.
checkUrl(INTENT_URL_WITH_JAVASCRIPT_FALLBACK_URL)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO | START_OTHER_ACTIVITY);
Intent invokedIntent = mDelegate.startActivityIntent;
Assert.assertTrue(invokedIntent.getData().toString().startsWith("market://"));
Assert.assertEquals(null, mDelegate.getNewUrlAfterClobbering());
Assert.assertEquals(null, mDelegate.getReferrerUrlForClobbering());
}
@Test
@SmallTest
public void testFallback_UseFallbackUrlForRedirectionFromTypedInUrl() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
checkUrl("http://goo.gl/abcdefg")
.withPageTransition(PageTransition.TYPED)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0);
checkUrl(INTENT_URL_WITH_FALLBACK_URL_WITHOUT_PACKAGE_NAME)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
// Now the user opens a link.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 1);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testIgnoreEffectiveRedirectFromIntentFallbackUrl() {
// We cannot resolve any intent, so fall-back URL will be used.
mDelegate.setCanResolveActivityForExternalSchemes(false);
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0);
checkUrl(INTENT_URL_WITH_CHAIN_FALLBACK_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
// As a result of intent resolution fallback, we have clobberred the current tab.
// The fall-back URL was HTTP-schemed, but it was effectively redirected to a new intent
// URL using javascript. However, we do not allow chained fallback intent, so we do NOT
// override URL loading here.
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 0);
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
// Now enough time (2 seconds) have passed.
// New URL loading should not be affected.
// (The URL happened to be the same as previous one.)
// TODO(changwan): this is not likely cause flakiness, but it may be better to refactor
// systemclock or pass the new time as parameter.
long lastUserInteractionTimeInMillis = SystemClock.elapsedRealtime() + 2 * 1000L;
redirectHandler.updateNewUrlLoading(
PageTransition.LINK, false, true, lastUserInteractionTimeInMillis, 1);
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
}
@Test
@SmallTest
public void testIgnoreEffectiveRedirectFromUserTyping() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, false, false, 0, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withPageTransition(PageTransition.TYPED)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.TYPED, true, false, 0, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withPageTransition(PageTransition.TYPED)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 0, 1);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNavigationFromLinkWithoutUserGesture() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void
testChromeAppInBackground() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.setIsChromeAppInForeground(false);
checkUrl(YOUTUBE_URL).expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNotChromeAppInForegroundRequired() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
mDelegate.setIsChromeAppInForeground(false);
checkUrl(YOUTUBE_URL)
.withChromeAppInForegroundRequired(false)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testCreatesIntentsToOpenInNewTab() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
mUrlHandler = new ExternalNavigationHandler(mDelegate);
ExternalNavigationParams params =
new ExternalNavigationParams.Builder(YOUTUBE_MOBILE_URL, false)
.setOpenInNewTab(true)
.build();
@OverrideUrlLoadingResult
int result = mUrlHandler.shouldOverrideUrlLoading(params);
Assert.assertEquals(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT, result);
Assert.assertTrue(mDelegate.startActivityIntent != null);
Assert.assertTrue(
mDelegate.startActivityIntent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false));
}
@Test
@SmallTest
public void testCanExternalAppHandleUrl() {
mDelegate.setCanResolveActivityForExternalSchemes(false);
mDelegate.add(new IntentActivity("some_app", "someapp"));
Assert.assertTrue(mUrlHandler.canExternalAppHandleUrl("some_app://some_app.com/"));
Assert.assertTrue(mUrlHandler.canExternalAppHandleUrl("wtai://wp/mc;0123456789"));
Assert.assertTrue(mUrlHandler.canExternalAppHandleUrl(
"intent:/#Intent;scheme=no_app;package=com.no_app;end"));
Assert.assertFalse(mUrlHandler.canExternalAppHandleUrl("no_app://no_app.com/"));
}
@Test
@SmallTest
public void testPlusAppRefresh() {
mDelegate.add(new IntentActivity(PLUS_STREAM_URL, "plus"));
checkUrl(PLUS_STREAM_URL)
.withReferrer(PLUS_STREAM_URL)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testSameDomainDifferentApps() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL)
.withReferrer(KEEP_URL)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testFormSubmitSameDomain() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL)
.withReferrer(KEEP_URL)
.withPageTransition(PageTransition.FORM_SUBMIT)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testBackgroundTabNavigation() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
checkUrl(YOUTUBE_URL)
.withIsBackgroundTabNavigation(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testPdfDownloadHappensInChrome() {
mDelegate.add(new IntentActivity(CALENDAR_URL, "calendar"));
checkUrl(CALENDAR_URL + "/file.pdf")
.expecting(OverrideUrlLoadingResult.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;")
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@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")
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
mDelegate.setPreviousUrl("https://refertest.com");
checkUrl("http://refertest.com").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testReferrerExtra() {
mDelegate.add(new IntentActivity(YOUTUBE_URL, YOUTUBE_PACKAGE_NAME));
String referrer = "http://www.google.com";
checkUrl("http://youtube.com:90/foo/bar")
.withReferrer(referrer)
.withPageTransition(PageTransition.FORM_SUBMIT)
.withIsRedirect(true)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertEquals(Uri.parse(referrer),
mDelegate.startActivityIntent.getParcelableExtra(Intent.EXTRA_REFERRER));
Assert.assertEquals(
1, mDelegate.startActivityIntent.getIntExtra(IntentHandler.EXTRA_REFERRER_ID, 0));
}
@Test
@SmallTest
public void testNavigationFromReload() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(PageTransition.RELOAD, false, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SmallTest
public void testNavigationWithForwardBack() {
mDelegate.add(new IntentActivity(YOUTUBE_MOBILE_URL, YOUTUBE_PACKAGE_NAME));
TabRedirectHandler redirectHandler = TabRedirectHandler.create();
redirectHandler.updateNewUrlLoading(
PageTransition.FORM_SUBMIT | PageTransition.FORWARD_BACK, false, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, true, false, 1, 0);
checkUrl(YOUTUBE_MOBILE_URL)
.withIsRedirect(true)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, false, 1, 1);
checkUrl(YOUTUBE_MOBILE_URL)
.withRedirectHandler(redirectHandler)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
@Test
@SuppressLint("SdCardPath")
@SmallTest
public void testFileAccess() {
String fileUrl = "file:///sdcard/Downloads/test.html";
mDelegate.shouldRequestFileAccess = false;
// Verify no overrides if file access is allowed (under different load conditions).
checkUrl(fileUrl).expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl)
.withPageTransition(PageTransition.RELOAD)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
checkUrl(fileUrl)
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
mDelegate.shouldRequestFileAccess = true;
// Verify that the file intent action is triggered if file access is not allowed.
checkUrl(fileUrl)
.withPageTransition(PageTransition.AUTO_TOPLEVEL)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION, START_FILE);
}
@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));
mDelegate.defaultSmsPackageName = TEXT_APP_2_PACKAGE_NAME;
checkUrl("sms:+012345678?body=hello%20there")
.withReferrer(referer)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Assert.assertEquals(TEXT_APP_2_PACKAGE_NAME, mDelegate.startActivityIntent.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.
mDelegate.defaultSmsPackageName = "text_app_3";
checkUrl("sms:+012345678?body=hello%20there")
.withReferrer(referer)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Assert.assertNull(mDelegate.startActivityIntent.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));
mDelegate.defaultSmsPackageName = TEXT_APP_2_PACKAGE_NAME;
checkUrl("intent://012345678?body=hello%20there/#Intent;scheme=sms;end")
.withReferrer(referer)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Assert.assertEquals(TEXT_APP_2_PACKAGE_NAME, mDelegate.startActivityIntent.getPackage());
}
/**
* Test that tapping a link which falls into the scope of the current webapp keeps the user in
* the webapp.
*/
@Test
@SmallTest
public void testLaunchWebApp_StayInSameWebApp() {
final String twaScope = "https://my_twa.org";
final String twaPackageName = "org.my_twa";
mDelegate.add(new IntentActivity(twaScope, twaPackageName)
.withWebappScopePolicy(WebappScopePolicy.Type.STRICT));
mDelegate.setReferrerWebappPackageName(twaPackageName);
checkUrl(twaScope + "/new.html").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
/**
* Test that when a webapp with "STRICT scope policy" is navigated outside of the webapp's scope
* by "tapping a link" that a Chrome Custom Tab is not launched.
*/
@Test
@SmallTest
public void testLeaveStrictWebapp_LinkOutOfScope() {
final String twaScope = "https://my_twa.org";
final String twaPackageName = "org.my_twa";
mDelegate.add(new IntentActivity(twaScope, twaPackageName)
.withWebappScopePolicy(WebappScopePolicy.Type.STRICT));
mDelegate.setReferrerWebappPackageName(twaPackageName);
checkUrl(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
/**
* Test that when a webapp with "STRICT scope policy" is navigated outside of the webapp's scope
* via "JavaScript while the webapp is in the background" that a CCT is not launched (as not to
* move the webapp to the foreground and annoy the user)
*/
@Test
@SmallTest
public void testLeaveStrictWebapp_JSBackgroundNavOutOfScope() {
final String twaScope = "https://my_twa.org";
final String twaPackageName = "org.my_twa";
mDelegate.add(new IntentActivity(twaScope, twaPackageName)
.withWebappScopePolicy(WebappScopePolicy.Type.STRICT));
mDelegate.setReferrerWebappPackageName(twaPackageName);
checkUrl(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withIsBackgroundTabNavigation(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
/**
* Test that when a webapp with "LEGACY scope policy" is navigated outside of the webapp's scope
* by "tapping a link" that a CCT is not launched.
*/
@Test
@SmallTest
public void testLeaveLegacyWebapp_LinkOutOfScope() {
final String twaScope = "https://my_twa.org";
final String twaPackageName = "org.my_twa";
mDelegate.add(new IntentActivity(twaScope, twaPackageName)
.withWebappScopePolicy(WebappScopePolicy.Type.LEGACY));
mDelegate.setReferrerWebappPackageName(twaPackageName);
checkUrl(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
}
/**
* 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).withIsWebApk(true));
checkUrl(WEBAPK_SCOPE)
.expecting(OverrideUrlLoadingResult.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, "org.chromium.webapk.with.native").withIsWebApk(true));
mDelegate.add(new IntentActivity(scope, "com.webapk.with.native.android"));
checkUrl(scope).expecting(
OverrideUrlLoadingResult.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 = "org.chromium.webapk.with.native";
final String scope1NativeAppPackageName = "com.webapk.with.native.android";
final String scope2 = "https://www.template.com";
mDelegate.add(new IntentActivity(scope1, scope1WebApkPackageName).withIsWebApk(true));
mDelegate.add(new IntentActivity(scope1, scope1NativeAppPackageName));
mDelegate.add(new IntentActivity(scope2, WEBAPK_PACKAGE_NAME).withIsWebApk(true));
mDelegate.setReferrerWebappPackageName(scope1WebApkPackageName);
checkUrl(scope2)
.withReferrer(scope1)
.expecting(OverrideUrlLoadingResult.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, WEBAPK_PACKAGE_NAME).withIsWebApk(false));
checkUrl(WEBAPK_SCOPE)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
}
@Test
@SmallTest
public void testMarketIntent_MarketInstalled() {
checkUrl("market://1234")
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertNotNull(mDelegate.startActivityIntent);
Assert.assertTrue(mDelegate.startActivityIntent.getScheme().startsWith("market"));
}
@Test
@SmallTest
public void testMarketIntent_MarketNotInstalled() {
mDelegate.setCanResolveActivityForMarket(false);
checkUrl("market://1234").expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
Assert.assertNull(mDelegate.startActivityIntent);
}
@Test
@SmallTest
public void testMarketIntent_ShowDialogIncognitoMarketInstalled() {
checkUrl("market://1234")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO | START_OTHER_ACTIVITY);
Assert.assertTrue(mDelegate.startIncognitoIntentCalled);
}
@Test
@SmallTest
public void testMarketIntent_DontShowDialogIncognitoMarketNotInstalled() {
mDelegate.setCanResolveActivityForMarket(false);
checkUrl("market://1234")
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.NO_OVERRIDE, IGNORE);
Assert.assertFalse(mDelegate.startIncognitoIntentCalled);
}
@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)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withHasUserGesture(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT,
START_OTHER_ACTIVITY);
Assert.assertTrue(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(
mDelegate.startActivityIntent));
Assert.assertFalse(mDelegate.startIncognitoIntentCalled);
}
@Test
@SmallTest
public void testUserGesture_Incognito() {
// IMDB app is installed.
mDelegate.add(new IntentActivity("imdb:", INTENT_APP_PACKAGE_NAME));
checkUrl(INTENT_URL_WITH_FALLBACK_URL)
.withReferrer(SEARCH_RESULT_URL_FOR_TOM_HANKS)
.withHasUserGesture(true)
.withIsIncognito(true)
.expecting(OverrideUrlLoadingResult.OVERRIDE_WITH_ASYNC_ACTION,
START_INCOGNITO | START_OTHER_ACTIVITY);
Assert.assertTrue(IntentWithGesturesHandler.getInstance().getUserGestureAndClear(
mDelegate.startActivityIntent));
Assert.assertTrue(mDelegate.startIncognitoIntentCalled);
}
private static ResolveInfo newResolveInfo(String packageName) {
ActivityInfo ai = new ActivityInfo();
ai.packageName = packageName;
ai.name = "Name: " + packageName;
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);
return info;
}
private static WebappInfo newWebappInfoFromScope(String scope) {
Intent webappIntent = WebappTestHelper.createMinimalWebappIntent("" /* id */, "" /* url */);
webappIntent.putExtra(ShortcutHelper.EXTRA_SCOPE, scope);
return WebappInfo.create(webappIntent);
}
private static class IntentActivity {
private String mUrlPrefix;
private String mPackageName;
private boolean mIsWebApk;
private @WebappScopePolicy.Type int mWebappScopePolicy;
public IntentActivity(String urlPrefix, String packageName) {
mUrlPrefix = urlPrefix;
mPackageName = packageName;
mWebappScopePolicy = WebappScopePolicy.Type.LEGACY;
}
public IntentActivity withIsWebApk(boolean isWebApk) {
mIsWebApk = isWebApk;
mWebappScopePolicy = WebappScopePolicy.Type.STRICT;
return this;
}
public IntentActivity withWebappScopePolicy(@WebappScopePolicy.Type int policy) {
mWebappScopePolicy = policy;
return this;
}
public String urlPrefix() {
return mUrlPrefix;
}
public String packageName() {
return mPackageName;
}
public boolean isWebApk() {
return mIsWebApk;
}
public @WebappScopePolicy.Type int webappScopePolicy() {
return mWebappScopePolicy;
}
public boolean isSpecialized() {
// Specialized if URL prefix is more than just a scheme.
return Pattern.compile("[^:/]+://.+").matcher(mUrlPrefix).matches();
}
}
private static class TestExternalNavigationDelegate implements ExternalNavigationDelegate {
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent) {
List<ResolveInfo> list = new ArrayList<>();
String dataString = intent.getDataString();
if (dataString.startsWith("http://") || dataString.startsWith("https://")) {
list.add(newResolveInfo("chrome"));
}
for (IntentActivity intentActivity : mIntentActivities) {
if (dataString.startsWith(intentActivity.urlPrefix())) {
list.add(newSpecializedResolveInfo(
intentActivity.packageName(), intentActivity));
}
}
if (!list.isEmpty()) return list;
String schemeString = intent.getData().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()));
}
return list;
}
@Override
public boolean willChromeHandleIntent(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 @WebappScopePolicy.NavigationDirective int applyWebappScopePolicyForUrl(String url) {
for (IntentActivity intentActivity : mIntentActivities) {
if (intentActivity.packageName().equals(mReferrerWebappPackageName)) {
WebappInfo info = newWebappInfoFromScope(intentActivity.urlPrefix());
return WebappScopePolicy.applyPolicyForNavigationToUrl(
intentActivity.webappScopePolicy(), info, url);
}
}
return WebappScopePolicy.NavigationDirective.NORMAL_BEHAVIOR;
}
@Override
public int countSpecializedHandlers(List<ResolveInfo> infos) {
int count = 0;
List<IntentActivity> matchingIntentActivities = findMatchingIntentActivities(infos);
for (IntentActivity intentActivity : matchingIntentActivities) {
if (intentActivity.isSpecialized()) {
++count;
}
}
return count;
}
private ArrayList<IntentActivity> findMatchingIntentActivities(List<ResolveInfo> infos) {
ArrayList<IntentActivity> outList = new ArrayList<IntentActivity>();
for (ResolveInfo info : infos) {
String packageName = info.activityInfo.packageName;
for (IntentActivity intentActivity : mIntentActivities) {
if (intentActivity.packageName().equals(packageName)) {
outList.add(intentActivity);
}
}
}
return outList;
}
@Override
public void startActivity(Intent intent, boolean proxy) {
startActivityIntent = intent;
}
@Override
public boolean startActivityIfNeeded(Intent intent, boolean proxy) {
// For simplicity, don't distinguish between startActivityIfNeeded and startActivity
// until a test requires this distinction.
startActivityIntent = intent;
mCalledWithProxy = proxy;
return true;
}
@Override
public boolean startIncognitoIntent(Intent intent, String referrerUrl, String fallbackUrl,
Tab tab, boolean needsToCloseTab, boolean proxy) {
startActivityIntent = intent;
startIncognitoIntentCalled = true;
return true;
}
@Override
public boolean shouldRequestFileAccess(String url) {
return shouldRequestFileAccess;
}
@Override
public void startFileIntent(Intent intent, String referrerUrl, boolean needsToCloseTab) {
startFileIntentCalled = true;
}
@Override
public @OverrideUrlLoadingResult int clobberCurrentTab(String url, String referrerUrl) {
mNewUrlAfterClobbering = url;
mReferrerUrlForClobbering = referrerUrl;
return OverrideUrlLoadingResult.OVERRIDE_WITH_CLOBBERING_TAB;
}
@Override
public void maybeSetWindowId(Intent intent) {
}
@Override
public void maybeRecordAppHandlersInIntent(Intent intent, List<ResolveInfo> info) {
}
@Override
public boolean isChromeAppInForeground() {
return mIsChromeAppInForeground;
}
@Override
public String getDefaultSmsPackageName() {
return defaultSmsPackageName;
}
@Override
public boolean isPdfDownload(String url) {
return url.endsWith(".pdf");
}
@Override
public boolean maybeLaunchInstantApp(
String url, String referrerUrl, boolean isIncomingRedirect) {
return mCanHandleWithInstantApp;
}
@Override
public boolean isSerpReferrer() {
return mIsSerpReferrer;
}
@Override
public String getPreviousUrl() {
return mPreviousUrl;
}
@Override
public boolean isIntentForTrustedCallingApp(Intent intent) {
return mIsCallingAppTrusted;
}
@Override
public boolean isValidWebApk(String packageName) {
for (IntentActivity activity : mIntentActivities) {
if (activity.packageName().equals(packageName)) {
return activity.isWebApk();
}
}
return false;
}
public void reset() {
startActivityIntent = null;
startIncognitoIntentCalled = false;
startFileIntentCalled = false;
startWebappCctIntentCalled = false;
mCalledWithProxy = false;
}
public void add(IntentActivity handler) {
mIntentActivities.add(handler);
}
public void setCanResolveActivityForExternalSchemes(boolean value) {
mCanResolveActivityForExternalSchemes = value;
}
public void setCanResolveActivityForMarket(boolean value) {
mCanResolveActivityForMarket = value;
}
public String getNewUrlAfterClobbering() {
return mNewUrlAfterClobbering;
}
public String getReferrerUrlForClobbering() {
return mReferrerUrlForClobbering;
}
public void setIsChromeAppInForeground(boolean value) {
mIsChromeAppInForeground = value;
}
public void setReferrerWebappPackageName(String webappPackageName) {
mReferrerWebappPackageName = webappPackageName;
}
public String getReferrerWebappPackageName() {
return mReferrerWebappPackageName;
}
public void setCanHandleWithInstantApp(boolean value) {
mCanHandleWithInstantApp = value;
}
public void setIsSerpReferrer(boolean value) {
mIsSerpReferrer = value;
}
public void setPreviousUrl(String value) {
mPreviousUrl = value;
}
public void setIsCallingAppTrusted(boolean trusted) {
mIsCallingAppTrusted = trusted;
}
public Intent startActivityIntent;
public boolean startIncognitoIntentCalled;
public boolean startFileIntentCalled;
public boolean startWebappCctIntentCalled;
public String defaultSmsPackageName;
private String mReferrerWebappPackageName;
private ArrayList<IntentActivity> mIntentActivities = new ArrayList<IntentActivity>();
private boolean mCanResolveActivityForExternalSchemes = true;
private boolean mCanResolveActivityForMarket = true;
private String mNewUrlAfterClobbering;
private String mReferrerUrlForClobbering;
private boolean mCanHandleWithInstantApp;
private boolean mIsSerpReferrer;
private String mPreviousUrl;
public boolean mCalledWithProxy;
public boolean mIsChromeAppInForeground = true;
private boolean mIsCallingAppTrusted;
public boolean shouldRequestFileAccess;
}
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) {
return new ExternalNavigationTestParams(url);
}
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 mHasUserGesture;
private TabRedirectHandler mRedirectHandler;
private ExternalNavigationTestParams(String url) {
mUrl = url;
}
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 withRedirectHandler(TabRedirectHandler handler) {
mRedirectHandler = handler;
return this;
}
public void expecting(
@OverrideUrlLoadingResult 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 expectStartWebappCct = (otherExpectation & START_WEBAPP_CCT) != 0;
boolean expectStartOtherActivity = (otherExpectation & START_OTHER_ACTIVITY) != 0;
boolean expectStartFile = (otherExpectation & START_FILE) != 0;
boolean expectSaneIntent = expectStartOtherActivity
&& (otherExpectation & INTENT_SANITIZATION_EXCEPTION) == 0;
boolean expectProxyForIA = (otherExpectation & PROXY_FOR_INSTANT_APPS) != 0;
mDelegate.reset();
ExternalNavigationParams params =
new ExternalNavigationParams
.Builder(mUrl, mIsIncognito, mReferrerUrl, mPageTransition, mIsRedirect)
.setApplicationMustBeInForeground(mChromeAppInForegroundRequired)
.setRedirectHandler(mRedirectHandler)
.setIsBackgroundTabNavigation(mIsBackgroundTabNavigation)
.setIsMainFrame(true)
.setNativeClientPackageName(mDelegate.getReferrerWebappPackageName())
.setHasUserGesture(mHasUserGesture)
.build();
@OverrideUrlLoadingResult
int result = mUrlHandler.shouldOverrideUrlLoading(params);
boolean startActivityCalled = false;
boolean startWebApkCalled = false;
if (mDelegate.startActivityIntent != null) {
startActivityCalled = true;
String packageName = mDelegate.startActivityIntent.getPackage();
if (packageName != null) {
startWebApkCalled =
packageName.startsWith(WebApkConstants.WEBAPK_PACKAGE_PREFIX);
}
}
Assert.assertEquals(expectedOverrideResult, result);
Assert.assertEquals(expectStartIncognito, mDelegate.startIncognitoIntentCalled);
Assert.assertEquals(expectStartWebappCct, mDelegate.startWebappCctIntentCalled);
Assert.assertEquals(expectStartActivity, startActivityCalled);
Assert.assertEquals(expectStartWebApk, startWebApkCalled);
Assert.assertEquals(expectStartFile, mDelegate.startFileIntentCalled);
Assert.assertEquals(expectProxyForIA, mDelegate.mCalledWithProxy);
if (startActivityCalled && expectSaneIntent) {
checkIntentSanity(mDelegate.startActivityIntent, "Intent");
if (mDelegate.startActivityIntent.getSelector() != null) {
checkIntentSanity(mDelegate.startActivityIntent.getSelector(),
"Intent's selector");
}
}
}
}
private static String getPackageName() {
return ContextUtils.getApplicationContext().getPackageName();
}
private static class TestPackageManager extends MockPackageManager {
private ExternalNavigationDelegate mDelegate;
public TestPackageManager(ExternalNavigationDelegate delegate) {
mDelegate = delegate;
}
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
return mDelegate.queryIntentActivities(intent);
}
}
private static class TestContext extends ContextWrapper {
private PackageManager mPackageManager;
public TestContext(Context baseContext, ExternalNavigationDelegate delegate) {
super(baseContext);
mPackageManager = new TestPackageManager(delegate);
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@Override
public String getPackageName() {
return "test.app.name";
}
@Override
public void startActivities(Intent[] intents, Bundle options) {
throw new UnsupportedOperationException();
}
@Override
public void startActivity(Intent intent) {
throw new UnsupportedOperationException();
}
@Override
public void startActivity(Intent intent, Bundle options) {
throw new UnsupportedOperationException();
}
}
}