Commit b1a0e716 authored by Michael Thiessen's avatar Michael Thiessen Committed by Commit Bot

Merge 2D and VR intent filters.

This is enabled by three things:
1. We now trust that 2D intents should launch 2D Chrome even if the user
is in a VR headset so we don't have to use expensive APIs on launch.
2. Deep-linked apps and the associated launcher workarounds have been
removed.
3. Daydream has stopped removing the Daydream category from its intents
that target launching in VR.

This should mean that Chrome no longer shows up multiple times in the
default browser picker, and third-party ROMs won't get confused by
Chrome having multiple intent handlers that support View intents.

Bug: 847921
Change-Id: I236fab049e00f64eb04d6fcb4a82f05f0d31f728
Reviewed-on: https://chromium-review.googlesource.com/1157272Reviewed-by: default avatarTibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#580344}
parent 3e237d96
......@@ -193,6 +193,17 @@ by a child template that "extends" this file.
android:taskAffinity=""
android:excludeFromRecents="true"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density">
<!-- TODO(mthiesse, b/72214458): This is a duplication of the icon metadata below.
Daydream will actually ignore the metadata here, and use the metadata on the
activity-alias. However, play store apk validation fails to find the icons on the
alias, so we need to include them here to pass validation. -->
{% if enable_vr == "true" %}
<meta-data android:name="com.google.android.vr.icon"
android:resource="@drawable/daydream_icon_foreground" />
<meta-data android:name="com.google.android.vr.icon_background"
android:resource="@drawable/daydream_icon_background" />
{% endif %}
{{ self.supports_vr() }}
</activity>
<activity-alias android:name="com.google.android.apps.chrome.IntentDispatcher"
android:targetActivity="org.chromium.chrome.browser.document.ChromeLauncherActivity"
......@@ -200,6 +211,9 @@ by a child template that "extends" this file.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
{% if enable_vr == "true" %}
<category android:name="com.google.intent.category.DAYDREAM" />
{% endif %}
</intent-filter>
<!-- Matches the common case of intents with no MIME type.
Make sure to keep in sync with the next filter. -->
......@@ -208,6 +222,9 @@ by a child template that "extends" this file.
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
{% if enable_vr == "true" %}
<category android:name="com.google.intent.category.DAYDREAM" />
{% endif %}
{% if channel in ['stable', 'default'] %}<data android:scheme="googlechrome" />{% endif %}
<data android:scheme="http" />
<data android:scheme="https" />
......@@ -218,13 +235,11 @@ by a child template that "extends" this file.
<!-- Same filter as above but with MIME types. Intents that
do not specify a MIME type won't match. -->
<intent-filter>
{% block common_view_intent_shared_filter_with_mime_body %}
{{ self.common_view_intent_shared_filter_body() }}
<data android:scheme="content" />
<data android:mimeType="text/html"/>
<data android:mimeType="text/plain"/>
<data android:mimeType="application/xhtml+xml"/>
{% endblock %}
</intent-filter>
<!-- MHTML support, used for snapshots -->
<intent-filter>
......@@ -246,6 +261,9 @@ by a child template that "extends" this file.
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
{% if enable_vr == "true" %}
<category android:name="com.google.intent.category.DAYDREAM" />
{% endif %}
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:host="*" />
......@@ -256,10 +274,8 @@ by a child template that "extends" this file.
<!-- Same filter as above but with mimeType="*/*". Used for
handling intent send by ShareIt. -->
<intent-filter>
{% block mhtml_view_intent_shared_filter_with_mime_body %}
{{ self.mhtml_view_intent_shared_filter_body() }}
<data android:mimeType="*/*"/>
{% endblock %}
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_SEARCH" />
......@@ -283,6 +299,12 @@ by a child template that "extends" this file.
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
{% if enable_vr == "true" %}
<meta-data android:name="com.google.android.vr.icon"
android:resource="@drawable/daydream_icon_foreground" />
<meta-data android:name="com.google.android.vr.icon_background"
android:resource="@drawable/daydream_icon_background" />
{% endif %}
</activity-alias>
<activity android:name="org.chromium.chrome.browser.media.MediaLauncherActivity"
......@@ -391,71 +413,6 @@ by a child template that "extends" this file.
{{ self.extra_web_rendering_activity_definitions() }}
</activity>
{% endfor %}
{% if enable_vr == "true" %}
<!-- Needs to be exported for Daydream deep-links. Also note that we explicitly don't set
VR mode here as it actually doesn't matter for VR launches given that we set it
within the timeout in onCreate, and it makes launches from 2D much smoother. -->
<activity android:name="org.chromium.chrome.browser.vr.VrMainActivity"
android:theme="@style/VrSupportThemeLauncher"
android:exported="true"
android:taskAffinity=""
android:relinquishTaskIdentity="true"
android:excludeFromRecents="true"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|mcc|mnc|screenLayout|smallestScreenSize|uiMode|density">
<!-- TODO(mthiesse, b/72214458): This is a duplication of the icon metadata below.
Daydream will actually ignore the metadata here, and use the metadata on the
activity-alias. However, play store apk validation fails to find the icons on the
alias, so we need to include them here to pass validation. -->
<meta-data android:name="com.google.android.vr.icon"
android:resource="@drawable/daydream_icon_foreground" />
<meta-data android:name="com.google.android.vr.icon_background"
android:resource="@drawable/daydream_icon_background" />
{{ self.supports_vr() }}
</activity>
<!-- The VrIntentDispatcher handles many of the same intents as the non-vr
IntentDispatcher, and should be kept in sync where applicable. Intents that should
not be handled in VR include all intents that don't launch ChromeTabbedActivity.
Custom Tabs are a complicated special case, which are kind of supported. -->
<activity-alias android:name="com.google.android.apps.chrome.VrIntentDispatcher"
android:targetActivity="org.chromium.chrome.browser.vr.VrMainActivity"
android:enabled="true"
android:exported="true">
<meta-data android:name="com.google.android.vr.icon"
android:resource="@drawable/daydream_icon_foreground" />
<meta-data android:name="com.google.android.vr.icon_background"
android:resource="@drawable/daydream_icon_background" />
<!-- The Intent filters must all have lower priority than the non-VR intent filters
so that intents without the Daydream flag aren't erroneously routed to the
VrIntentDispatcher. -->
<intent-filter android:priority="-1">
<!-- Show icon in Daydream app launcher. -->
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.intent.category.DAYDREAM" />
</intent-filter>
<!-- Intent filters below are copied from IntentDispatcher, but with the added
Daydream flag. -->
<intent-filter android:priority="-1">
<category android:name="com.google.intent.category.DAYDREAM" />
{{ self.common_view_intent_shared_filter_body() }}
</intent-filter>
<intent-filter android:priority="-1">
<category android:name="com.google.intent.category.DAYDREAM" />
{{ self.common_view_intent_shared_filter_with_mime_body() }}
</intent-filter>
<intent-filter android:priority="-1">
<category android:name="com.google.intent.category.DAYDREAM" />
{{ self.mhtml_view_intent_shared_filter_body() }}
</intent-filter>
<intent-filter android:priority="-1">
<category android:name="com.google.intent.category.DAYDREAM" />
{{ self.mhtml_view_intent_shared_filter_with_mime_body() }}
</intent-filter>
</activity-alias>
{% endif %}
<!-- ChromeTabbedActivity related -->
<activity android:name="org.chromium.chrome.browser.ChromeTabbedActivity"
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 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. -->
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="VrSupportThemeLauncher" parent="@android:style/Theme.NoDisplay">
<item name="android:windowDisablePreview">true</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>
......@@ -20,6 +20,7 @@ import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsSessionToken;
import android.support.customtabs.TrustedWebUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
......@@ -51,6 +52,7 @@ import org.chromium.ui.widget.Toast;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.UUID;
/**
......@@ -384,6 +386,24 @@ public class LaunchIntentDispatcher implements IntentHandler.IntentHandlerDelega
*/
@SuppressLint("InlinedApi")
private @Action int dispatchToTabbedActivity() {
if (mIsVrIntent) {
for (WeakReference<Activity> weakActivity : ApplicationStatus.getRunningActivities()) {
final Activity activity = weakActivity.get();
if (activity == null) continue;
if (activity instanceof ChromeTabbedActivity) {
if (VrModuleProvider.getDelegate().willChangeDensityInVr(
(ChromeActivity) activity)) {
// In the rare case that entering VR will trigger a density change (and
// hence an Activity recreation), just return to Daydream home and kill the
// process, as there's no good way to recreate without showing 2D UI
// in-headset.
mActivity.finish();
System.exit(0);
}
}
}
}
maybePrefetchDnsInBackground();
Intent newIntent = new Intent(mIntent);
......
......@@ -26,8 +26,11 @@ public class ChromeLauncherActivity extends Activity {
try {
super.onCreate(savedInstanceState);
// VR Intents should only ever get routed through the VrMainActivity.
assert !VrModuleProvider.getIntentDelegate().isVrIntent(getIntent());
if (VrModuleProvider.getIntentDelegate().isVrIntent(getIntent())) {
// We need to turn VR mode on as early as possible in the intent handling flow to
// avoid brightness flickering when handling VR intents.
VrModuleProvider.getDelegate().setVrModeEnabled(this, true);
}
@LaunchIntentDispatcher.Action
int dispatchAction = LaunchIntentDispatcher.dispatch(this, getIntent());
......
......@@ -37,4 +37,5 @@ public interface VrDelegate {
boolean bootsToVr();
boolean isDaydreamReadyDevice();
boolean isDaydreamCurrentViewer();
boolean willChangeDensityInVr(ChromeActivity activity);
}
......@@ -143,4 +143,10 @@ import org.chromium.chrome.browser.ChromeActivity;
assert false;
return null;
}
@Override
public boolean willChangeDensityInVr(ChromeActivity activity) {
assert false;
return false;
}
}
......@@ -162,4 +162,9 @@ import org.chromium.chrome.browser.ChromeActivity;
public Bundle getVrIntentOptions(Context context) {
return VrIntentUtils.getVrIntentOptions(context);
}
@Override
public boolean willChangeDensityInVr(ChromeActivity activity) {
return VrShellDelegate.willChangeDensityInVr(activity);
}
}
......@@ -18,18 +18,7 @@ import org.chromium.chrome.R;
* Utilities dealing with extracting information about VR intents.
*/
public class VrIntentUtils {
// The Daydream Home app adds this extra to auto-present intents.
public static final String AUTOPRESENT_WEVBVR_EXTRA = "browser.vr.AUTOPRESENT_WEBVR";
public static final String DAYDREAM_CATEGORY = "com.google.intent.category.DAYDREAM";
// Tells Chrome not to relaunch itself when receiving a VR intent. This is used by tests since
// the relaunch logic does not work properly with the DON flow skipped.
public static final String AVOID_RELAUNCH_EXTRA =
"org.chromium.chrome.browser.vr.AVOID_RELAUNCH";
// Tells Chrome to attempts a relaunch of the intent if it is received outside of VR and doesn't
// have the Daydream category set. This is a workaround for https://crbug.com/854327 where
// launchInVr can sometimes launch the given intent before entering VR.
public static final String ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA =
"org.chromium.chrome.browser.vr.ENABLE_TEST_RELUANCH_WORKAROUND";
static final String VR_FRE_INTENT_EXTRA = "org.chromium.chrome.browser.vr.VR_FRE";
......
// Copyright 2017 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.vr;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.Log;
import org.chromium.base.TraceEvent;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.util.IntentUtils;
import java.lang.ref.WeakReference;
/**
* This is the VR equivalent of {@link ChromeLauncherActivity}. It exists because the Android
* platform doesn't inherently support hybrid VR Activities (like Chrome uses). All VR intents for
* Chrome are routed through this activity as its manifest entry contains VR specific attributes to
* ensure a smooth transition into Chrome VR, and allows us to properly handle both implicit and
* explicit VR intents that may be missing VR categories when they get sent.
*
* This Activity doesn't inherit from ChromeLauncherActivity because we need to be able to finish
* and relaunch this Launcher without calling ChromeLauncherActivity#onCreate which would fire the
* intent we don't yet want to fire.
*/
public class VrMainActivity extends Activity {
private static final String TAG = "VrMainActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
TraceEvent.begin("VrMainActivity.onCreate");
try {
super.onCreate(savedInstanceState);
boolean hasDaydreamCategory = getIntent().hasCategory(VrIntentUtils.DAYDREAM_CATEGORY);
if (!VrShellDelegate.deviceSupportsVrLaunches()
|| (!hasDaydreamCategory && !VrShellDelegate.isInVrSession())) {
// TODO(https://crbug.com/854327): Remove this workaround once the issue with
// launchInVr sometimes launching the given intent before entering VR is fixed.
if (IntentUtils.safeGetBooleanExtra(getIntent(),
VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, false)) {
Log.d(TAG,
"Relaunching given intent due to test workaround boolean being set.");
// Disable the workaround after the first time so we don't get into a loop
getIntent().putExtra(
VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, false);
VrIntentUtils.launchInVr(getIntent(), this);
finish();
return;
}
StringBuilder error = new StringBuilder("Attempted to launch Chrome into VR ");
if (!VrShellDelegate.deviceSupportsVrLaunches()) {
error.append("on a device that doesn't support Chrome in VR.");
} else {
error.append("without first being in a VR session or supplying the Daydream "
+ "category. Did you mean to use DaydreamApi#launchInVr()?");
}
Log.e(TAG, error.toString());
// Remove VR flags and re-target back to the 2D launcher.
VrIntentUtils.removeVrExtras(getIntent());
getIntent().setClass(this, ChromeLauncherActivity.class);
// Daydream drops the MAIN action for unknown reasons...
if (getIntent().getAction() == null) getIntent().setAction(Intent.ACTION_MAIN);
startActivity(getIntent());
finish();
return;
}
// If the launcher was launched from a 2D context (without calling
// DaydreamApi#launchInVr), then we need to relaunch the launcher in VR to allow
// downstream Activities to make assumptions about whether they're in VR or not, and
// ensure the VR configuration is set before launching Activities as they use the VR
// configuration to set style attributes.
boolean needsRelaunch;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
needsRelaunch = !VrShellDelegate.isInVrSession();
} else {
Configuration config = getResources().getConfiguration();
int uiMode = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
needsRelaunch = uiMode != Configuration.UI_MODE_TYPE_VR_HEADSET;
}
// The check for relaunching does not work properly if the DON flow is skipped, which
// is the case during tests. So, allow intents to specify that relaunching is not
// necessary.
if (IntentUtils.safeGetBooleanExtra(
getIntent(), VrIntentUtils.AVOID_RELAUNCH_EXTRA, false)) {
needsRelaunch = false;
}
if (needsRelaunch) {
// Under some situations, like with the skip DON flow developer setting on, we can
// get stuck in a relaunch loop as the VR Headset configuration won't get set. Add
// an extra to never relaunch more than once.
getIntent().putExtra(VrIntentUtils.AVOID_RELAUNCH_EXTRA, true);
VrIntentUtils.launchInVr(getIntent(), this);
finish();
return;
}
// We don't set VrMode for the launcher in the manifest because that causes weird things
// to happen when you send a VR intent to Chrome from a non-VR app, so we need to set it
// here.
VrShellDelegate.setVrModeEnabled(this, true);
// Daydream likes to remove the Daydream category from explicit intents for some reason.
// Since only implicit intents with the Daydream category can be routed here, it's safe
// to assume that the intent *should* have the Daydream category, which simplifies our
// intent handling.
getIntent().addCategory(VrIntentUtils.DAYDREAM_CATEGORY);
for (WeakReference<Activity> weakActivity : ApplicationStatus.getRunningActivities()) {
final Activity activity = weakActivity.get();
if (activity == null) continue;
if (activity instanceof ChromeActivity) {
if (VrShellDelegate.willChangeDensityInVr((ChromeActivity) activity)) {
// In the rare case that entering VR will trigger a density change (and
// hence an Activity recreation), just return to Daydream home and kill the
// process, as there's no good way to recreate without showing 2D UI
// in-headset.
finish();
System.exit(0);
}
}
}
@LaunchIntentDispatcher.Action
int dispatchAction = LaunchIntentDispatcher.dispatch(this, getIntent());
switch (dispatchAction) {
case LaunchIntentDispatcher.Action.FINISH_ACTIVITY:
finish();
break;
case LaunchIntentDispatcher.Action.FINISH_ACTIVITY_REMOVE_TASK:
ApiCompatibilityUtils.finishAndRemoveTask(this);
break;
default:
assert false : "Intent dispatcher finished with action " + dispatchAction
+ ", finishing anyway";
finish();
break;
}
} finally {
TraceEvent.end("VrMainActivity.onCreate");
}
}
}
......@@ -1624,7 +1624,6 @@ chrome_vr_java_sources = [
"java/src/org/chromium/chrome/browser/vr/keyboard/GvrKeyboardLoaderClient.java",
"java/src/org/chromium/chrome/browser/vr/keyboard/TextEditAction.java",
"java/src/org/chromium/chrome/browser/vr/keyboard/VrInputMethodManagerWrapper.java",
"java/src/org/chromium/chrome/browser/vr/VrMainActivity.java",
"java/src/org/chromium/chrome/browser/vr/EmptySniffingVrViewContainer.java",
"java/src/org/chromium/chrome/browser/vr/NoopCanvas.java",
"java/src/org/chromium/chrome/browser/vr/VrAlertDialog.java",
......
......@@ -151,8 +151,7 @@ public class VrBrowserTransitionTest {
// Send a VR intent, which will open the link in a CTA.
final String url =
VrBrowserTestFramework.getFileUrlForHtmlTestFile("test_navigation_2d_page");
VrBrowserTransitionUtils.sendVrLaunchIntent(
url, false /* autopresent */, true /* avoidRelaunch */);
VrBrowserTransitionUtils.sendVrLaunchIntent(url);
// Wait until we enter VR and have the correct URL.
VrBrowserTransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
......
......@@ -14,9 +14,9 @@ import org.junit.Assert;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.vr.TestVrShellDelegate;
import org.chromium.chrome.browser.vr.VrIntentUtils;
import org.chromium.chrome.browser.vr.VrMainActivity;
import org.chromium.chrome.browser.vr.VrShellDelegate;
import org.chromium.chrome.browser.vr.VrShellImpl;
import org.chromium.content.browser.test.util.CriteriaHelper;
......@@ -97,34 +97,19 @@ public class VrBrowserTransitionUtils extends VrTransitionUtils {
}
/**
* Sends an intent to Chrome telling it to launch in VR mode. If the given autopresent param is
* true, this is expected to fail unless the trusted intent check is disabled in
* VrShellDelegate.
* Sends an intent to Chrome telling it to launch in VR mode.
*
* @param url String containing the URL to open.
* @param autopresent If this intent is expected to auto-present WebVR.
* @param avoidRelaunch Include an extra that prevents relaunching Chrome once the intent is
* received.
*/
public static void sendVrLaunchIntent(String url, boolean autopresent, boolean avoidRelaunch) {
public static void sendVrLaunchIntent(String url) {
// Create an intent that will launch Chrome at the specified URL.
final Intent intent =
new Intent(ContextUtils.getApplicationContext(), VrMainActivity.class);
new Intent(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
intent.addCategory(VrIntentUtils.DAYDREAM_CATEGORY);
VrIntentUtils.setupVrIntent(intent);
if (autopresent) {
// Daydream removes this category for deep-linked URLs for legacy reasons.
intent.removeCategory(VrIntentUtils.DAYDREAM_CATEGORY);
intent.putExtra(VrIntentUtils.AUTOPRESENT_WEVBVR_EXTRA, true);
}
if (avoidRelaunch) intent.putExtra(VrIntentUtils.AVOID_RELAUNCH_EXTRA, true);
// TODO(https://crbug.com/854327): Remove this workaround once the issue with launchInVr
// sometimes launching the given intent before entering VR is fixed.
intent.putExtra(VrIntentUtils.ENABLE_TEST_RELAUNCH_WORKAROUND_EXTRA, true);
ThreadUtils.runOnUiThreadBlocking(
() -> { VrShellDelegate.getVrDaydreamApi().launchInVr(intent); });
}
......
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