Commit 58f68d3c authored by bsheedy's avatar bsheedy Committed by Commit Bot

Add VrCore head tracking support to instrumentation tests

Adds support for using the VrCore head tracking service in VR
instrumentation tests. This can be used either via an annotation, which
results in slightly faster test execution, or directly in a test via a
utility class.

Bug: 828190
Change-Id: Id4f806939b7242c084ac467bed2214b7b0c73911
Reviewed-on: https://chromium-review.googlesource.com/996901
Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548537}
parent 75a35d66
......@@ -645,10 +645,12 @@ if (enable_vr) {
"javatests/src/org/chromium/chrome/browser/vr_shell/nfc_apk/SimNfcActivity.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/ChromeTabbedActivityVrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/CustomTabActivityVrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/HeadTrackingMode.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/VrActivityRestriction.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/VrActivityRestrictionRule.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/VrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/rules/WebappActivityVrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/util/HeadTrackingUtils.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/util/NfcSimUtils.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/util/TransitionUtils.java",
"javatests/src/org/chromium/chrome/browser/vr_shell/util/VrInfoBarUtils.java",
......
......@@ -27,6 +27,7 @@ import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr_shell.rules.ChromeTabbedActivityVrTestRule;
import org.chromium.chrome.browser.vr_shell.rules.HeadTrackingMode;
import org.chromium.chrome.browser.vr_shell.util.TransitionUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
......@@ -95,6 +96,7 @@ public class VrShellDialogTest {
@Test
@Manual
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void microphoneDialogTest() throws InterruptedException, TimeoutException {
// Display audio permissions prompt.
displayDialog(
......@@ -110,6 +112,7 @@ public class VrShellDialogTest {
@Test
@Manual
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void cameraDialogTest() throws InterruptedException, TimeoutException {
// Display Camera permissions prompt.
displayDialog(
......@@ -125,6 +128,7 @@ public class VrShellDialogTest {
@Test
@Manual
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void locationDialogTest() throws InterruptedException, TimeoutException {
// Display Location permissions prompt.
displayDialog("test_navigation_2d_page",
......@@ -140,6 +144,7 @@ public class VrShellDialogTest {
@Test
@Manual
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void notificationDialogTest() throws InterruptedException, TimeoutException {
// Display Notification permissions prompt.
displayDialog("test_navigation_2d_page", "Notification.requestPermission(()=>{})");
......@@ -154,6 +159,7 @@ public class VrShellDialogTest {
@Test
@Manual
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void midiDialogTest() throws InterruptedException, TimeoutException {
// Display MIDI permissions prompt.
displayDialog("test_navigation_2d_page", "navigator.requestMIDIAccess({sysex: true})");
......
......@@ -9,6 +9,7 @@ import org.junit.runners.model.Statement;
import org.chromium.chrome.browser.vr_shell.TestVrShellDelegate;
import org.chromium.chrome.browser.vr_shell.rules.VrActivityRestriction.SupportedActivity;
import org.chromium.chrome.browser.vr_shell.util.HeadTrackingUtils;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
/**
......@@ -17,14 +18,22 @@ import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
*/
public class ChromeTabbedActivityVrTestRule
extends ChromeTabbedActivityTestRule implements VrTestRule {
private boolean mTrackerDirty;
@Override
public Statement apply(final Statement base, Description desc) {
public Statement apply(final Statement base, final Description desc) {
return super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
ChromeTabbedActivityVrTestRule.this, desc);
startMainActivityOnBlankPage();
TestVrShellDelegate.createTestVrShellDelegate(getActivity());
base.evaluate();
try {
base.evaluate();
} finally {
if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
}
}
}, desc);
}
......@@ -33,4 +42,14 @@ public class ChromeTabbedActivityVrTestRule
public SupportedActivity getRestriction() {
return SupportedActivity.CTA;
}
@Override
public boolean isTrackerDirty() {
return mTrackerDirty;
}
@Override
public void setTrackerDirty() {
mTrackerDirty = true;
}
}
......@@ -13,21 +13,30 @@ import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
import org.chromium.chrome.browser.vr_shell.TestVrShellDelegate;
import org.chromium.chrome.browser.vr_shell.rules.VrActivityRestriction.SupportedActivity;
import org.chromium.chrome.browser.vr_shell.util.HeadTrackingUtils;
/**
* VR extension of CustomTabActivityTestRule. Applies CustomTabActivityTestRule then
* opens up a CustomTabActivity to a blank page.
*/
public class CustomTabActivityVrTestRule extends CustomTabActivityTestRule implements VrTestRule {
private boolean mTrackerDirty;
@Override
public Statement apply(final Statement base, Description desc) {
public Statement apply(final Statement base, final Description desc) {
return super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
CustomTabActivityVrTestRule.this, desc);
startCustomTabActivityWithIntent(CustomTabsTestUtils.createMinimalCustomTabIntent(
InstrumentationRegistry.getTargetContext(), "about:blank"));
TestVrShellDelegate.createTestVrShellDelegate(getActivity());
base.evaluate();
try {
base.evaluate();
} finally {
if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
}
}
}, desc);
}
......@@ -36,4 +45,14 @@ public class CustomTabActivityVrTestRule extends CustomTabActivityTestRule imple
public SupportedActivity getRestriction() {
return SupportedActivity.CCT;
}
@Override
public boolean isTrackerDirty() {
return mTrackerDirty;
}
@Override
public void setTrackerDirty() {
mTrackerDirty = true;
}
}
// 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.
package org.chromium.chrome.browser.vr_shell.rules;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* An annotation for setting the VrCore head tracking service's tracking mode during pre-test setup.
*
* The benefit of setting the mode this way instead of via HeadTrackingUtils during a test is that
* starting services is asynchronous with no good way of waiting until whatever the service does
* takes effect. When set during a test, the test must idle long enough to safely assume that the
* service has taken effect. When applied during test setup, the Chrome startup period acts as the
* wait, as Chrome startup is slow enough that it's safe to assume the service has started by the
* time Chrome is ready.
*
* For example, the following would cause a test to start with its head position locked looking
* straight forward:
* <code>
* @HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
* </code>
* If a test is not annotated with this, it will use whatever mode is currently set. This should
* usually be the normal, sensor-based tracker, but is not guaranteed.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HeadTrackingMode {
public enum SupportedMode {
FROZEN, // Locked looking straight forward.
SWEEP, // Rotates back and forth horizontally in a 180 degree arc.
ROTATE, // Rotates 360 degrees.
CIRCLE_STRAFE, // Rotates 360 degrees, and if 6DOF is supported, changes position.
MOTION_SICKNESS // Moves in a figure-eight-like pattern.
}
/**
* @return The supported mode.
*/
public SupportedMode value();
}
......@@ -15,4 +15,14 @@ public interface VrTestRule {
* Get the VrActivityRestriction.SupportedActivity that this rule is restricted to running in.
*/
public SupportedActivity getRestriction();
/**
* Whether the head tracking mode has been changed.
*/
public boolean isTrackerDirty();
/**
* Tells the rule that the head tracking mode has been changed.
*/
public void setTrackerDirty();
}
......@@ -9,6 +9,7 @@ import org.junit.runners.model.Statement;
import org.chromium.chrome.browser.vr_shell.TestVrShellDelegate;
import org.chromium.chrome.browser.vr_shell.rules.VrActivityRestriction.SupportedActivity;
import org.chromium.chrome.browser.vr_shell.util.HeadTrackingUtils;
import org.chromium.chrome.browser.webapps.WebappActivityTestRule;
/**
......@@ -16,14 +17,22 @@ import org.chromium.chrome.browser.webapps.WebappActivityTestRule;
* up a WebappActivity to a blank page.
*/
public class WebappActivityVrTestRule extends WebappActivityTestRule implements VrTestRule {
private boolean mTrackerDirty;
@Override
public Statement apply(final Statement base, Description desc) {
public Statement apply(final Statement base, final Description desc) {
return super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
HeadTrackingUtils.checkForAndApplyHeadTrackingModeAnnotation(
WebappActivityVrTestRule.this, desc);
startWebappActivity();
TestVrShellDelegate.createTestVrShellDelegate(getActivity());
base.evaluate();
try {
base.evaluate();
} finally {
if (isTrackerDirty()) HeadTrackingUtils.revertTracker();
}
}
}, desc);
}
......@@ -32,4 +41,14 @@ public class WebappActivityVrTestRule extends WebappActivityTestRule implements
public SupportedActivity getRestriction() {
return SupportedActivity.WAA;
}
@Override
public boolean isTrackerDirty() {
return mTrackerDirty;
}
@Override
public void setTrackerDirty() {
mTrackerDirty = true;
}
}
// 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.
package org.chromium.chrome.browser.vr_shell.util;
import android.content.ComponentName;
import android.content.Intent;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.runner.Description;
import org.chromium.chrome.browser.vr_shell.rules.HeadTrackingMode;
import org.chromium.chrome.browser.vr_shell.rules.HeadTrackingMode.SupportedMode;
import org.chromium.chrome.browser.vr_shell.rules.VrTestRule;
import java.util.Arrays;
/**
* Utility class for interacting with the VrCore head tracking service, which allows fake head
* poses to be submitted instead of using actual sensor data.
*
* Requires that either the O2 rendering path is enabled or EnableVrCoreHeadTracking is set to true
* in the shared prefs file in order to actually work.
*/
public class HeadTrackingUtils {
private static final ComponentName HEAD_TRACKING_COMPONENT = new ComponentName(
"com.google.vr.vrcore", "com.google.vr.vrcore.tracking.HeadTrackingService");
private static final String ACTION_SET_TRACKER_TYPE = "com.google.vr.vrcore.SET_TRACKER_TYPE";
private static final String ACTION_SET_FAKE_TRACKER_MODE =
"com.google.vr.vrcore.SET_FAKE_TRACKER_MODE";
private static final String ACTION_SET_FAKE_TRACKER_POSE =
"com.google.vr.vrcore.SET_FAKE_TRACKER_POSE";
private static final String EXTRA_FAKE_TRACKER_MODE = "com.google.vr.vrcore.FAKE_TRACKER_MODE";
private static final String EXTRA_FAKE_TRACKER_POSE = "com.google.vr.vrcore.FAKE_TRACKER_POSE";
private static final String EXTRA_TRACKER_TYPE = "com.google.vr.vrcore.TRACKER_TYPE";
private static final int HEAD_TRACKING_APPLICATION_DELAY_MS = 500;
/**
* Class for holding data necessary to set the head tracking service's head pose to an
* arbitarary, static value. Contains either a quaternion or set of rotation Euler angles
* describing the direction to look in and an optional position in room space if 6DOF is
* supported.
*/
public static class FakePose {
private float[] mQuaternion;
private float[] mRotationEulerAngles;
private float[] mRoomSpacePosition;
public FakePose setQuaternion(float x, float y, float z, float w) {
mQuaternion = new float[] {x, y, z, w};
mRotationEulerAngles = null;
return this;
}
public FakePose setRotationEulerAngles(float rollDeg, float pitchDeg, float yawDeg) {
mRotationEulerAngles = new float[] {rollDeg, pitchDeg, yawDeg};
mQuaternion = null;
return this;
}
public FakePose setRoomSpacePosition(float x, float y, float z) {
mRoomSpacePosition = new float[] {x, y, z};
return this;
}
public FakePose clearRoomSpacePosition() {
mRoomSpacePosition = null;
return this;
}
public float[] getDataForExtra() {
if (mQuaternion == null && mRotationEulerAngles == null) {
throw new IllegalArgumentException(
"Tried to get FakePose data without setting either quaternion/angle data");
}
float[] orientationArray =
mRotationEulerAngles == null ? mQuaternion : mRotationEulerAngles;
if (mRoomSpacePosition == null) return orientationArray;
float[] combinedArray = Arrays.copyOf(
orientationArray, orientationArray.length + mRoomSpacePosition.length);
for (int i = 0; i < mRoomSpacePosition.length; i++) {
combinedArray[i + orientationArray.length] = mRoomSpacePosition[i];
}
return combinedArray;
}
}
/**
* Checks for the presence of a HeadTrackingMode annotation, and if found, sets the tracking
* mode to the specified value. If no annotation is found, the tracking mode is left at whatever
* the existing value is.
*
* @param rule The VrTestRule used by the current test case.
* @param desc The JUnit4 Description for the current test case.
*/
public static void checkForAndApplyHeadTrackingModeAnnotation(
VrTestRule rule, Description desc) {
// Check if the test has a HeadTrackingMode annotation
HeadTrackingMode annotation = desc.getAnnotation(HeadTrackingMode.class);
if (annotation == null) return;
applyHeadTrackingModeInternal(rule, annotation.value());
}
/**
* Sets the tracker type to the given mode and waits long enough to safely assume that the
* service has started.
*
* @param rule The VrTestRule used by the current test case.
* @param mode The HeadTrackingMode.SupportedMode value to set the fake head tracker mode to.
*/
public static void applyHeadTrackingMode(VrTestRule rule, SupportedMode mode) {
applyHeadTrackingModeInternal(rule, mode);
// TODO(bsheedy): Remove this sleep if the head tracking service ever exposes a way to be
// notified when a setting has been applied.
SystemClock.sleep(HEAD_TRACKING_APPLICATION_DELAY_MS);
}
/**
* Sets the head pose to the pose described by the given FakePose and waits long enough to
* safely assume that the pose has taken effect.
*
* @param rule The VrTestRule used by the current test case.
* @param pose The FakePose instance containing the pose data that will be sent to the head
* tracking service.
*/
public static void setHeadPose(VrTestRule rule, FakePose pose) {
restartHeadTrackingServiceIfNecessary(rule);
// Set the head pose to the given value
Intent poseIntent = new Intent(ACTION_SET_FAKE_TRACKER_POSE);
poseIntent.putExtra(EXTRA_FAKE_TRACKER_POSE, pose.getDataForExtra());
poseIntent.setComponent(HEAD_TRACKING_COMPONENT);
Assert.assertTrue(InstrumentationRegistry.getContext().startService(poseIntent) != null);
rule.setTrackerDirty();
// TODO(bsheedy): Remove this sleep. Could either expose poses up to Java and wait until
// we receive a pose that's the same as the one we set or see if the head tracking service
// adds the requested functionality of sending a notification when it's done applying
// settings.
SystemClock.sleep(HEAD_TRACKING_APPLICATION_DELAY_MS);
}
/**
* Reverts the tracking type back to values that a regular user would have (using real sensor
* data).
*
* Only meant to be called by rules after a test has run. Reseting the tracker to use sensor
* data during a test technically works, but messes up orientation if done while still in VR.
* until VR is exited and re-entered.
*/
public static void revertTracker() {
Intent typeIntent = new Intent(ACTION_SET_TRACKER_TYPE);
typeIntent.putExtra(EXTRA_TRACKER_TYPE, "sensor");
typeIntent.setComponent(HEAD_TRACKING_COMPONENT);
InstrumentationRegistry.getContext().startService(typeIntent);
}
public static String supportedModeToString(SupportedMode mode) {
switch (mode) {
case FROZEN:
return "frozen";
case SWEEP:
return "sweep";
case ROTATE:
return "rotate";
case CIRCLE_STRAFE:
return "circle_strafe";
case MOTION_SICKNESS:
return "motion_sickness";
default:
return "unknown_mode";
}
}
private static void applyHeadTrackingModeInternal(VrTestRule rule, SupportedMode mode) {
restartHeadTrackingServiceIfNecessary(rule);
// Set the fake tracker mode to the given value.
Intent modeIntent = new Intent(ACTION_SET_FAKE_TRACKER_MODE);
modeIntent.putExtra(EXTRA_FAKE_TRACKER_MODE, supportedModeToString(mode));
modeIntent.setComponent(HEAD_TRACKING_COMPONENT);
Assert.assertTrue(InstrumentationRegistry.getContext().startService(modeIntent) != null);
rule.setTrackerDirty();
}
private static void restartHeadTrackingServiceIfNecessary(VrTestRule rule) {
// If the tracker has already been dirtied, then we can assume that the tracker type
// has already been set to "fake".
if (rule.isTrackerDirty()) return;
// VR sessions from previous tests can somehow interfere with the setting of the tracker
// type, even if said previous sessions did not touch the head tracking service. Killing
// the service before attempting to set the tracker type appears to work around this
// issue.
// TODO(https://crbug.com/829127): Remove this once the root cause is fixed.
Intent stopIntent = new Intent();
stopIntent.setComponent(HEAD_TRACKING_COMPONENT);
InstrumentationRegistry.getContext().stopService(stopIntent);
// Set the tracker tracker type to "fake".
Intent typeIntent = new Intent(ACTION_SET_TRACKER_TYPE);
typeIntent.putExtra(EXTRA_TRACKER_TYPE, "fake");
typeIntent.setComponent(HEAD_TRACKING_COMPONENT);
Assert.assertTrue(InstrumentationRegistry.getContext().startService(typeIntent) != null);
rule.setTrackerDirty();
}
}
\ No newline at end of file
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