Commit b6edf54e authored by Colin Blundell's avatar Colin Blundell Committed by Commit Bot

[WebLayer] Match CCT's handling of http(s) URLs with external handlers

This CL changes WebLayer's intent launching logic to match that of CCT
in the specific case of an http(s) URL with a non-default external
handler. In this case CCT overrides ExternalNavigationHandler's default
behavior of showing the intent picker to instead keep the navigation in
the app. It is desired that WebLayer precisely match CCT here.

The implementation is adapted from that in //chrome's
CustomTabDelegateFactory. A couple notes:

- Unlike //chrome, in //weblayer the check for whether the app has a
  default handler does not bother to exclude the embedder of WebLayer.
  The reason is that WebLayer is not embedded in apps that handle
  incoming intents.
- The special-casing of the intent being for the client of CCT at [1]
  has no analogue in WebLayer (again, because WebLayer is not embedded
  in apps that handle incoming intents). Thus, there is no such
  corresponding check in WebLayer.

To test:

- Install the booking.com app on your device
- Search for "hotel booking paris" in Google on WebLayer Shell
- Click on the ad for booking.com that should result at the top
- Observe that the booking.com app is launched
- Via Android Settings, configure the booking.com's handling of support
  URLs to be "ask every time"
- Repeat the above search in WebLayer Shell and observe that the
  navigation to booking.com stays in WebLayer Shell

[1]  https://source.chromium.org/chromium/chromium/src/+/master:chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java;l=119?q=CustomTabDelegateFactory&ss=chromium&originalUrl=https:%2F%2Fcs.chromium.org%2F

Bug: 1085405
Change-Id: I2bec60967c5c2ef1638a927a33f7e21ba93ae58a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2219494
Commit-Queue: Colin Blundell <blundell@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#773134}
parent 00fb8fc2
......@@ -4,6 +4,7 @@
package org.chromium.weblayer.test;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
......@@ -32,7 +33,27 @@ public class ExternalNavigationTest {
public InstrumentationActivityTestRule mActivityTestRule =
new InstrumentationActivityTestRule();
/**
* A dummy activity that claims to handle "weblayer://weblayertest".
*/
public static class DummyActivityForSpecialScheme extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finish();
}
}
private static final String ABOUT_BLANK_URL = "about:blank";
private static final String CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER =
"weblayer://weblayertest/intent";
private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING =
CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER;
private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_ACTION =
"android.intent.action.VIEW";
// The package is not specified in the intent that gets created when navigating to the special
// scheme.
private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_PACKAGE = null;
private static final String INTENT_TO_CHROME_DATA_CONTENT =
"play.google.com/store/apps/details?id=com.facebook.katana/";
private static final String INTENT_TO_CHROME_SCHEME = "https";
......@@ -75,6 +96,9 @@ public class ExternalNavigationTest {
private final String mNonResolvableIntentWithFallbackUrl =
NON_RESOLVABLE_INTENT + mTestServerSiteFallbackUrlExtra + "end";
private final String mRedirectToCustomSchemeUrlWithDefaultExternalHandler =
mActivityTestRule.getTestServer().getURL(
"/server-redirect?" + CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER);
private final String mRedirectToIntentToChromeURL =
mActivityTestRule.getTestServer().getURL("/server-redirect?" + INTENT_TO_CHROME_URL);
private final String mNonResolvableIntentWithFallbackUrlThatLaunchesIntent =
......@@ -168,6 +192,38 @@ public class ExternalNavigationTest {
Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
}
/**
* Tests that a navigation that redirects to a URL with a special scheme that has a default
* external handler results in an external intent being launched.
*/
@Test
@SmallTest
public void testRedirectToCustomSchemeUrlWithDefaultExternalHandlerLaunchesIntent()
throws Throwable {
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL);
IntentInterceptor intentInterceptor = new IntentInterceptor();
activity.setIntentInterceptor(intentInterceptor);
Tab tab = mActivityTestRule.getActivity().getTab();
TestThreadUtils.runOnUiThreadBlocking(() -> {
tab.getNavigationController().navigate(
Uri.parse(mRedirectToCustomSchemeUrlWithDefaultExternalHandler));
});
intentInterceptor.waitForIntent();
// The current URL should not have changed, and the intent should have been launched.
Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
Intent intent = intentInterceptor.mLastIntent;
Assert.assertNotNull(intent);
Assert.assertEquals(
INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_PACKAGE, intent.getPackage());
Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_ACTION, intent.getAction());
Assert.assertEquals(
INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING, intent.getDataString());
}
/**
* Tests that clicking on a link that goes to an external intent in the same tab results in the
* external intent being launched.
......
......@@ -6,8 +6,11 @@ package org.chromium.weblayer_private;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import org.chromium.base.ContextUtils;
import org.chromium.base.PackageManagerUtils;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.external_intents.ExternalNavigationDelegate;
import org.chromium.components.external_intents.ExternalNavigationDelegate.StartActivityIfNeededResult;
import org.chromium.components.external_intents.ExternalNavigationHandler;
......@@ -68,6 +71,18 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
Intent intent, boolean proxy) {
assert !proxy
: "|proxy| should be true only for instant apps, which WebLayer doesn't handle";
boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(intent.toUri(0));
boolean hasDefaultHandler = hasDefaultHandler(intent);
// Match CCT's custom behavior of keeping http(s) URLs with no default handler in the app.
// TODO(blundell): If/when CCT eliminates its special handling of this case, eliminate it
// from WebLayer as well.
if (!isExternalProtocol && !hasDefaultHandler) {
return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
}
// Otherwise defer to ExternalNavigationHandler's default logic.
return StartActivityIfNeededResult.DID_NOT_HANDLE;
}
......@@ -199,4 +214,14 @@ public class ExternalNavigationDelegateImpl implements ExternalNavigationDelegat
String browserFallbackUrl, boolean isGoogleReferrer) {
return false;
}
/**
* Resolve the default external handler of an intent.
* @return Whether the default external handler is found.
*/
private boolean hasDefaultHandler(Intent intent) {
ResolveInfo info = PackageManagerUtils.resolveActivity(intent, 0);
if (info == null) return false;
return info.match != 0;
}
}
......@@ -41,7 +41,16 @@
<activity android:name="TelemetryActivity"
android:theme="@android:style/Theme.Holo.Light.NoActionBar">
</activity>
{% if weblayer_package is defined %}
<activity android:name="org.chromium.weblayer.test.ExternalNavigationTest$DummyActivityForSpecialScheme"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="weblayertest" android:scheme="weblayer" />
</intent-filter>
</activity>
{% if weblayer_package is defined %}
<meta-data android:name="org.chromium.weblayer.WebLayerPackage"
android:value="{{ weblayer_package }}"/>
{% endif %}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment