blob: 59e7e29e70211e2e61b8f0112815e6c1184baf99 [file] [log] [blame]
// 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.content.Context;
import android.os.SystemClock;
import com.google.vr.testframework.controller.ControllerTestApi;
import org.junit.Assert;
import java.util.concurrent.TimeUnit;
/**
* Wrapper for the ControllerTestApi class to handle more complex actions such
* as clicking and dragging.
*
* Requires that VrCore's settings file is modified to use the test API:
* - UseAutomatedController: true
* - PairedControllerDriver: "DRIVER_AUTOMATED"
* - PairedControllerAddress: "FOO"
*/
public class EmulatedVrController {
public enum ScrollDirection { UP, DOWN, LEFT, RIGHT }
private final ControllerTestApi mApi;
public EmulatedVrController(Context context) {
mApi = new ControllerTestApi(context);
}
public ControllerTestApi getApi() {
return mApi;
}
public void sendClickButtonToggleEvent() {
mApi.buttonEvent.sendClickButtonToggleEvent();
}
/**
* Presses and quickly releases the Daydream controller's touchpad button.
* Or, if the button is already pressed, releases and quickly presses again.
*/
public void pressReleaseTouchpadButton() {
mApi.buttonEvent.sendClickButtonEvent();
}
/**
* Presses and quickly releases the Daydream controller's app button.
* Or, if the button is already pressed, releases and quickly presses again.
*/
public void pressReleaseAppButton() {
mApi.buttonEvent.sendAppButtonEvent();
}
/**
* Holds the home button to recenter the view using an arbitrary, but valid
* orientation quaternion.
*/
public void recenterView() {
mApi.buttonEvent.sendHomeButtonToggleEvent();
// A valid position must be sent a short time after the home button
// is pressed in order for recentering to actually complete, and no
// way to be signalled that we should send the event, so sleep
SystemClock.sleep(500);
// We don't care where the controller is pointing when recentering occurs as long
// as it results in a successful recenter, so send an arbitrary, valid orientation
mApi.moveEvent.sendMoveEvent(0.0f, 0.0f, 0.0f, 1.0f);
mApi.buttonEvent.sendHomeButtonToggleEvent();
}
/**
* Performs a short home button press/release, which launches the Daydream Home app.
*/
public void goToDaydreamHome() {
mApi.buttonEvent.sendShortHomeButtonEvent();
}
/**
* Simulates a touch down-drag-touch up sequence on the touchpad between two points.
*
* @param xStart the x coordinate to start the touch sequence at, in range [0.0f, 1.0f]
* @param yStart the y coordinate to start the touch sequence at, in range [0.0f, 1.0f]
* @param xEnd the x coordinate to end the touch sequence at, in range [0.0f, 1.0f]
* @param yEnd the y coordinate to end the touch sequence at, in range [0.0f, 1.0f]
* @param steps the number of steps the drag will have
* @param speed how long to wait between steps in the sequence. Generally, higher numbers
* result in faster movement, e.g. when used for scrolling, a higher number results in faster
* scrolling.
*/
public void performLinearTouchpadMovement(
float xStart, float yStart, float xEnd, float yEnd, int steps, int speed) {
// Touchpad events have timestamps attached to them in nanoseconds - for smooth scrolling,
// the timestamps should increase at a similar rate to the amount of time we actually wait
// between sending events, which is determined by the given speed.
long simulatedDelay = TimeUnit.MILLISECONDS.toNanos(speed);
long timestamp = mApi.touchEvent.startTouchSequence(xStart, yStart, simulatedDelay, speed);
timestamp = mApi.touchEvent.dragFromTo(
xStart, yStart, xEnd, yEnd, steps, timestamp, simulatedDelay, speed);
mApi.touchEvent.endTouchSequence(xEnd, yEnd, timestamp, simulatedDelay, speed);
}
/**
* Performs an swipe on the touchpad in order to scroll in the specified
* direction while in the VR browser.
* Note that scrolling this way is not consistent, i.e. scrolling down then
* scrolling up at the same speed won't necessarily scroll back to the exact
* starting position on the page.
*
* @param direction the ScrollDirection to scroll with
* @param steps the number of intermediate steps to send while scrolling
* @param speed how long to wait between steps in the scroll, with higher
* numbers resulting in a faster scroll
*/
public void scroll(ScrollDirection direction, int steps, int speed) {
float startX, startY, endX, endY;
startX = startY = endX = endY = 0.5f;
switch (direction) {
case UP:
startY = 0.1f;
endY = 0.9f;
break;
case DOWN:
startY = 0.9f;
endY = 0.1f;
break;
case LEFT:
startX = 0.1f;
endX = 0.9f;
break;
case RIGHT:
startX = 0.9f;
endX = 0.1f;
break;
default:
Assert.fail("Unknown scroll direction enum given");
}
performLinearTouchpadMovement(startX, startY, endX, endY, steps, speed);
}
/**
* Touches then releases the touchpad to cancel fling scroll.
*/
public void cancelFlingScroll() {
// Arbitrary amount of delay to both ensure that the touchpad press is properly registered
// and long enough that we don't accidentally trigger any functionality bound to quick
// touchpad taps if there is any.
int delay = 500;
long simulatedDelay = TimeUnit.MILLISECONDS.toNanos(delay);
long timestamp = mApi.touchEvent.startTouchSequence(0.5f, 0.5f, simulatedDelay, delay);
mApi.touchEvent.endTouchSequence(0.5f, 0.5f, timestamp, simulatedDelay, delay);
}
/**
* Instantly moves the controller to the specified quaternion coordinates.
*
* @param x the x component of the quaternion
* @param y the y component of the quaternion
* @param z the z component of the quaternion
* @param w the w component of the quaternion
*/
public void moveControllerInstant(float x, float y, float z, float w) {
mApi.moveEvent.sendMoveEvent(x, y, z, w, 0);
}
/**
* Moves the controller from one position to another over a period of time.
*
* @param startAngles the x/y/z angles to start the controller at, in radians
* @param endAngles the x/y/z angles to end the controller at, in radians
* @param steps the number of steps the controller will take moving between the positions
* @param delayBetweenSteps how long to sleep between positions
*/
public void moveControllerInterpolated(
float[] startAngles, float[] endAngles, int steps, int delayBetweenSteps) {
if (startAngles.length != 3 || endAngles.length != 3) {
throw new IllegalArgumentException("Angle arrays must be length 3");
}
mApi.moveEvent.sendMoveEvent(new float[] {startAngles[0], endAngles[0]},
new float[] {startAngles[1], endAngles[1]},
new float[] {startAngles[2], endAngles[2]}, steps, delayBetweenSteps);
}
/**
* Touch and release the touchpad to perform a controller click.
*/
public void performControllerClick() {
// pressReleaseTouchpadButton() appears to be flaky for clicking on things, as sometimes
// it happens too fast for Chrome to register. So, manually press and release with a delay
sendClickButtonToggleEvent();
SystemClock.sleep(50);
sendClickButtonToggleEvent();
}
// TODO(bsheedy): Add support for more complex actions, e.g. click/drag/release
}