blob: 3068ce10f25fcef4a2764ec8e508c15277636060 [file] [log] [blame]
// 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.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.rules.HeadTrackingMode;
import org.chromium.chrome.browser.vr.rules.HeadTrackingMode.SupportedMode;
import org.chromium.chrome.browser.vr.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();
}
}