Commit 7caa6679 authored by bsheedy's avatar bsheedy Committed by Commit Bot

Support VR instrumentation tests on standalone devices

Adds support for running the existing VR instrumentation tests on
standalone VR devices, specifically the Lenovo Mirage Solo.

Main changes include:
1. Adding new restrictions to differentiate smartphone VR from
   standalone devices and only enable tests on standalone when they make
   sense to do so.
2. Work around tests starting in VR.
3. Fix various issues with running on standalone devices such as
   controller recentering behavior being slightly different.

Bug: 876946
Change-Id: Ibb357e765768c0db9d08a8c7504ee8d14e0d31f3
Reviewed-on: https://chromium-review.googlesource.com/1186114Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#585895}
parent 17a92a4b
......@@ -779,6 +779,7 @@ if (enable_vr || (enable_arcore && package_arcore)) {
"javatests/src/org/chromium/chrome/browser/vr/rules/ChromeTabbedActivityVrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/CustomTabActivityVrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/HeadTrackingMode.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/VrActivityRestrictionRule.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/VrSettingsFile.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/VrTestRule.java",
"javatests/src/org/chromium/chrome/browser/vr/rules/WebappActivityVrTestRule.java",
......
......@@ -157,6 +157,7 @@ public class VrShellDelegate
private static boolean sRegisteredVrAssetsComponent;
private static @VrSupportLevel Integer sVrSupportLevel;
private static Boolean sBootsToVr = null;
private static boolean sTestVrShellDelegateOnStartup;
private ChromeActivity mActivity;
......@@ -728,6 +729,10 @@ public class VrShellDelegate
return isDaydreamReadyDevice() && DaydreamApi.supports2dInVr(context);
}
protected static void enableTestVrShellDelegateOnStartupForTesting() {
sTestVrShellDelegateOnStartup = true;
}
/* package */ static boolean isVrModeEnabled(Activity activity) {
return sVrModeEnabledActivitys.contains(activity);
}
......@@ -1010,12 +1015,29 @@ public class VrShellDelegate
return getInstance((ChromeActivity) activity);
}
@SuppressWarnings("unchecked")
private static VrShellDelegate getInstance(ChromeActivity activity) {
if (!LibraryLoader.getInstance().isInitialized()) return null;
if (activity == null || !activitySupportsPresentation(activity)) return null;
if (sInstance != null) return sInstance;
ThreadUtils.assertOnUiThread();
if (sTestVrShellDelegateOnStartup) {
try {
// This should only ever be run during tests on standalone devices. Normally, we
// create a TestVrShellDelegate during pre-test setup after Chrome has started.
// However, since Chrome is started in VR on standalones, creating a
// TestVrShellDelegate after startup discards the existing VrShellDelegate instance
// that's in use, which is bad. So, in those cases, create a TestVrShellDelegate
// instead of the production version.
Class clazz = Class.forName("org.chromium.chrome.browser.vr.TestVrShellDelegate");
Method method = clazz.getMethod("createTestVrShellDelegate", ChromeActivity.class);
method.invoke(null, activity);
} catch (Exception e) {
assert false;
}
} else {
sInstance = new VrShellDelegate(activity);
}
return sInstance;
}
......
......@@ -96,13 +96,9 @@ public class EmulatedVrController {
*/
public void recenterView() {
getApi().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
// Need to "hold" the button long enough to trigger a view recenter instead of just a button
// press - half a second is sufficient and non-flaky.
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
getApi().moveEvent.sendMoveEvent(0.0f, 0.0f, 0.0f, 1.0f);
getApi().buttonEvent.sendHomeButtonToggleEvent();
}
......
......@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.vr;
import android.graphics.PointF;
import android.os.Build;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.ChromeActivity;
......@@ -24,6 +25,7 @@ public class TestVrShellDelegate extends VrShellDelegate {
private Boolean mAllow2dIntents;
public static void createTestVrShellDelegate(final ChromeActivity activity) {
if (sInstance != null) return;
ThreadUtils.runOnUiThreadBlocking(() -> { sInstance = new TestVrShellDelegate(activity); });
}
......@@ -39,6 +41,14 @@ public class TestVrShellDelegate extends VrShellDelegate {
return TestVrShellDelegate.getInstance().getVrShell().isDisplayingUrlForTesting();
}
public static boolean isOnStandalone() {
return Build.DEVICE.equals("vega");
}
public static void enableTestVrShellDelegateOnStartupForTesting() {
VrShellDelegate.enableTestVrShellDelegateOnStartupForTesting();
}
protected TestVrShellDelegate(ChromeActivity activity) {
super(activity);
}
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import android.support.test.filters.MediumTest;
import android.view.ViewGroup;
......@@ -14,6 +16,7 @@ import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
......@@ -30,6 +33,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Restriction(RESTRICTION_TYPE_SVR)
public class VrBrowserCompositorViewHolderTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......
......@@ -8,8 +8,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.graphics.PointF;
import android.support.test.filters.MediumTest;
......@@ -45,7 +44,7 @@ import java.util.concurrent.atomic.AtomicReference;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VIEWER_DAYDREAM})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public class VrBrowserControllerInputTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......
......@@ -8,6 +8,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.graphics.PointF;
import android.support.test.InstrumentationRegistry;
......@@ -42,7 +43,7 @@ import java.util.concurrent.TimeoutException;
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.
Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=VrBrowsingNativeAndroidUi"})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public class VrBrowserDialogTest {
// A long enough sleep after entering VR to ensure that the VR entry animations are complete.
private static final int VR_ENTRY_SLEEP_MS = 1000;
......@@ -105,7 +106,7 @@ public class VrBrowserDialogTest {
// Display the given permission prompt.
VrBrowserTransitionUtils.forceEnterVrBrowserOrFail(POLL_TIMEOUT_LONG_MS);
mVrBrowserTestFramework.runJavaScriptOrFail(promptCommand, POLL_TIMEOUT_SHORT_MS);
mVrBrowserTestFramework.runJavaScriptOrFail(promptCommand, POLL_TIMEOUT_LONG_MS);
VrBrowserTransitionUtils.waitForNativeUiPrompt(POLL_TIMEOUT_LONG_MS);
// There is currently no way to know whether a dialog has been drawn yet,
......@@ -159,11 +160,13 @@ public class VrBrowserDialogTest {
}
/**
* Test navigate to 2D page and launch the Camera dialog.
* Test navigate to 2D page and launch the Camera dialog. Not valid on standalones because
* there is no camera permission.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
public void testCameraPermissionPrompt() throws InterruptedException, TimeoutException {
// Display Camera permissions prompt.
navigateAndDisplayPermissionPrompt(
......
......@@ -7,7 +7,7 @@ package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.browser.vr.XrTestFramework.NATIVE_URLS_OF_INTEREST;
import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
......@@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-webvr"})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public class VrBrowserNativeUiTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......
......@@ -9,6 +9,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.graphics.PointF;
import android.support.test.InstrumentationRegistry;
......@@ -52,7 +53,7 @@ import java.util.concurrent.TimeoutException;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-webvr"})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public class VrBrowserNavigationTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......@@ -607,6 +608,7 @@ public class VrBrowserNavigationTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
public void testNewTabAutomaticallyOpenedWhenIncognitoClosed()
throws InterruptedException, TimeoutException {
VrBrowserTransitionUtils.forceExitVr();
......
......@@ -15,6 +15,8 @@ import org.chromium.chrome.test.ChromeActivityTestRule;
public class VrBrowserTestFramework extends XrTestFramework {
public VrBrowserTestFramework(ChromeActivityTestRule rule) {
super(rule);
if (!TestVrShellDelegate.isOnStandalone()) {
Assert.assertFalse("Test started in VR", VrShellDelegate.isInVr());
}
}
}
......@@ -10,6 +10,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_M
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_NON_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.app.Activity;
import android.content.Context;
......@@ -173,7 +174,7 @@ public class VrBrowserTransitionTest {
* browser when Chrome gets a VR intent, and returns to 2D when Chrome receives a 2D Intent.
*/
@Test
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@MediumTest
public void test2dIntentExitsVrShell() {
TestVrShellDelegate.getInstance().setAllow2dIntents(true);
......@@ -238,7 +239,7 @@ public class VrBrowserTransitionTest {
*/
@Test
@CommandLineFlags.Add("enable-webvr")
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@MediumTest
public void testExitPresentationWebVrToVrShell()
throws IllegalArgumentException, InterruptedException, TimeoutException {
......@@ -253,7 +254,7 @@ public class VrBrowserTransitionTest {
*/
@Test
@CommandLineFlags.Add("enable-features=WebXR")
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@MediumTest
public void testExitPresentationWebXrToVrShell()
throws IllegalArgumentException, InterruptedException, TimeoutException {
......@@ -290,7 +291,7 @@ public class VrBrowserTransitionTest {
*/
@Test
@CommandLineFlags.Add("enable-webvr")
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@MediumTest
public void testWebVrReEntryFromVrBrowser() throws InterruptedException, TimeoutException {
reEntryFromVrBrowserImpl(
......@@ -304,7 +305,7 @@ public class VrBrowserTransitionTest {
*/
@Test
@CommandLineFlags.Add("enable-features=WebXR")
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@MediumTest
public void testWebXrReEntryFromVrBrowser() throws InterruptedException, TimeoutException {
reEntryFromVrBrowserImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile(
......
......@@ -7,8 +7,7 @@ package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL_SHORT_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import android.graphics.PointF;
import android.support.test.filters.MediumTest;
......@@ -34,7 +33,7 @@ import org.chromium.content.browser.test.util.CriteriaHelper;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_VIEWER_DAYDREAM})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public class VrBrowserWebInputEditingTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......
......@@ -8,6 +8,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import android.support.test.filters.MediumTest;
......@@ -35,7 +36,7 @@ import java.util.concurrent.TimeoutException;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-webvr"})
@Restriction(RESTRICTION_TYPE_DEVICE_DAYDREAM)
@Restriction({RESTRICTION_TYPE_DEVICE_DAYDREAM, RESTRICTION_TYPE_SVR})
public class VrFeedbackInfoBarTest {
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import android.os.Build;
import android.support.test.filters.MediumTest;
import android.view.View;
......@@ -21,6 +23,7 @@ import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr.rules.XrActivityRestriction;
......@@ -42,6 +45,7 @@ import java.util.concurrent.Callable;
@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT) // WebVR is only supported on K+
@Restriction(RESTRICTION_TYPE_SVR)
public class VrInstallUpdateInfoBarTest {
@ClassParameter
private static List<ParameterSet> sClassParams =
......@@ -53,7 +57,7 @@ public class VrInstallUpdateInfoBarTest {
public VrInstallUpdateInfoBarTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mVrTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mVrTestRule);
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mVrTestRule);
}
/**
......
......@@ -58,7 +58,7 @@ public class WebXrArSessionTest {
public WebXrArSessionTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = XrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule);
mRuleChain = XrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
......
......@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.browser.vr.XrTestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import android.os.Build;
import android.support.test.filters.MediumTest;
......@@ -22,6 +23,7 @@ import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr.rules.XrActivityRestriction;
import org.chromium.chrome.browser.vr.util.VrShellDelegateUtils;
......@@ -53,7 +55,7 @@ public class WebXrVrDeviceTest {
public WebXrVrDeviceTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule);
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
......@@ -87,6 +89,7 @@ public class WebXrVrDeviceTest {
@MediumTest
@CommandLineFlags.Add("enable-features=WebXROrientationSensorDevice")
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
@Restriction(RESTRICTION_TYPE_SVR)
public void testGvrlessMagicWindowCapabilities() throws InterruptedException {
// Make Chrome think that VrCore is not installed
VrShellDelegateUtils.setVrCoreCompatibility(VrCoreCompatibility.VR_NOT_AVAILABLE);
......
......@@ -9,6 +9,7 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_NON_DAYDREAM;
import android.os.Build;
......@@ -73,7 +74,7 @@ public class WebXrVrInputTest {
public WebXrVrInputTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule);
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
......@@ -152,7 +153,7 @@ public class WebXrVrInputTest {
*/
@Test
@LargeTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testControllerClicksRegisteredOnDaydream() throws InterruptedException {
EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
......@@ -193,7 +194,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
......@@ -359,7 +360,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public void testAppButtonExitsPresentation() throws InterruptedException {
appButtonExitsPresentationImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("generic_webvr_page"),
......@@ -372,7 +373,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
......@@ -487,7 +488,7 @@ public class WebXrVrInputTest {
@DisabledTest(message = "crbug.com/859666")
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testFocusUpdatesSynchronously() throws InterruptedException {
mWebVrTestFramework.loadUrlAndAwaitInitialization(
......@@ -517,7 +518,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public void testAppButtonAfterPageStopsSubmitting() throws InterruptedException {
appButtonAfterPageStopsSubmittingImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("webvr_page_submits_once"),
......@@ -530,7 +531,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
......@@ -559,7 +560,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
......@@ -594,7 +595,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR,WebXRGamepadSupport"})
......@@ -628,7 +629,7 @@ public class WebXrVrInputTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags.Remove({"enable-webvr"})
@XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
public void testWebXrGamepadNotReturnedWithoutAnyFeatures() throws InterruptedException {
......
......@@ -49,7 +49,7 @@ public class WebXrVrTabTest {
public WebXrVrTabTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule);
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
......
......@@ -17,8 +17,10 @@ import org.chromium.content_public.browser.WebContents;
public class WebXrVrTestFramework extends WebXrTestFramework {
public WebXrVrTestFramework(ChromeActivityTestRule rule) {
super(rule);
if (!TestVrShellDelegate.isOnStandalone()) {
Assert.assertFalse("Test started in VR", VrShellDelegate.isInVr());
}
}
/**
* VR-specific implementation of enterSessionWithUserGesture that includes a workaround for
......
......@@ -10,7 +10,9 @@ import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_CHECK_INTERVAL
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.XrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_DEVICE_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_SVR;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VR_SETTINGS_SERVICE;
import android.annotation.TargetApi;
......@@ -77,7 +79,7 @@ public class WebXrVrTransitionTest {
public WebXrVrTransitionTest(Callable<ChromeActivityTestRule> callable) throws Exception {
mTestRule = callable.call();
mRuleChain = VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule);
mRuleChain = VrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule);
}
@Before
......@@ -124,6 +126,8 @@ public class WebXrVrTransitionTest {
final UiDevice uiDevice =
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Screenshots are just black on standalones, so skip this part in that case.
if (!TestVrShellDelegate.isOnStandalone()) {
CriteriaHelper.pollInstrumentationThread(
()
-> {
......@@ -146,8 +150,9 @@ public class WebXrVrTransitionTest {
}
return false;
},
"Immersive session started, but browser not visibly in VR", POLL_TIMEOUT_LONG_MS,
POLL_CHECK_INTERVAL_LONG_MS);
"Immersive session started, but browser not visibly in VR",
POLL_TIMEOUT_LONG_MS, POLL_CHECK_INTERVAL_LONG_MS);
}
framework.assertNoJavaScriptErrors();
}
......@@ -312,6 +317,7 @@ public class WebXrVrTransitionTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_SVR)
public void testControlsVisibleAfterExitingVr() throws InterruptedException {
controlsVisibleAfterExitingVrImpl(
WebVrTestFramework.getFileUrlForHtmlTestFile("generic_webvr_page"),
......@@ -326,6 +332,7 @@ public class WebXrVrTransitionTest {
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
@Restriction(RESTRICTION_TYPE_SVR)
public void testControlsVisibleAfterExitingVr_WebXr() throws InterruptedException {
controlsVisibleAfterExitingVrImpl(
WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
......@@ -405,7 +412,7 @@ public class WebXrVrTransitionTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
public void testRendererKilledInWebVrStaysInVr()
throws IllegalArgumentException, InterruptedException, TimeoutException {
rendererKilledInVrStaysInVrImpl(
......@@ -418,7 +425,7 @@ public class WebXrVrTransitionTest {
*/
@Test
@MediumTest
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
@CommandLineFlags
.Remove({"enable-webvr"})
@CommandLineFlags.Add({"enable-features=WebXR"})
......
......@@ -162,8 +162,8 @@ parameterized class. The general things you will need to are:
* Define a constructor for your test class that takes a
`Callable<ChromeActivityTestRule>`. This constructor must set `mVrTestRule` to
the `Callable`'s `call()` return value and set `mRuleChain` to the return
value of `XrTestRuleUtils.wrapRuleInXrActivityRestrictionRule(mTestRule)`
for AR tests or `VrTestRuleUtils.wrapRuleInXrActivityRestrictionRule
value of `XrTestRuleUtils.wrapRuleInActivityRestrictionRule(mTestRule)`
for AR tests or `VrTestRuleUtils.wrapRuleInActivityRestrictionRule
(mTestRule)` for VR tests.
### Add The New File
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.vr.rules;
import android.content.Intent;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
......@@ -60,4 +62,10 @@ public class ChromeTabbedActivityVrTestRule
public void setDonEnabled(boolean isEnabled) {
mDonEnabled = isEnabled;
}
@Override
public Intent prepareUrlIntent(Intent intent, String url) {
super.prepareUrlIntent(intent, url);
return VrTestRuleUtils.maybeAddStandaloneIntentData(intent);
}
}
......@@ -31,9 +31,10 @@ public class CustomTabActivityVrTestRule extends CustomTabActivityTestRule imple
VrTestRuleUtils.evaluateVrTestRuleImpl(
base, desc, CustomTabActivityVrTestRule.this, () -> {
startCustomTabActivityWithIntent(
VrTestRuleUtils.maybeAddStandaloneIntentData(
CustomTabsTestUtils.createMinimalCustomTabIntent(
InstrumentationRegistry.getTargetContext(),
"about:blank"));
"about:blank")));
TestVrShellDelegate.createTestVrShellDelegate(getActivity());
});
}
......
// 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.rules;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.chromium.chrome.browser.vr.TestVrShellDelegate;
import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActivity;
import org.chromium.chrome.browser.vr.util.XrTestRuleUtils;
/**
* Rule that conditionally skips a test if the current VrTestRule's Activity is not
* one of the supported Activity types for the test.
*/
public class VrActivityRestrictionRule implements TestRule {
private SupportedActivity mCurrentRestriction;
public VrActivityRestrictionRule(SupportedActivity currentRestriction) {
mCurrentRestriction = currentRestriction;
}
@Override
public Statement apply(final Statement base, final Description desc) {
// Currently, we don't have any VR-specific logic except for standalone devices.
if (!TestVrShellDelegate.isOnStandalone()) {
return base;
}
// We can only run tests in ChromeTabbedActivity on standalones, so ignore if the current
// activity isn't a CTA. XrActivityRestrictionRule will take care of ensuring that the test
// actually supports running in a CTA.
return mCurrentRestriction == SupportedActivity.CTA ? base
: generateStandaloneIgnoreStatement();
}
private Statement generateStandaloneIgnoreStatement() {
return new Statement() {
@Override
public void evaluate() {
Assume.assumeTrue("Test ignored because "
+ XrTestRuleUtils.supportedActivityToString(mCurrentRestriction)
+ " is not a supported activity on standalone devices.",
false);
}
};
}
}
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.vr.rules;
import android.content.Intent;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
......@@ -58,4 +60,9 @@ public class WebappActivityVrTestRule extends WebappActivityTestRule implements
public void setDonEnabled(boolean isEnabled) {
mDonEnabled = isEnabled;
}
@Override
public Intent createIntent() {
return VrTestRuleUtils.maybeAddStandaloneIntentData(super.createIntent());
}
}
......@@ -13,7 +13,7 @@ import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActiv
import org.chromium.chrome.browser.vr.util.XrTestRuleUtils;
/**
* Rule that conditionally skips a test if the current VrTestRule's Activity is not
* Rule that conditionally skips a test if the current XrTestRule's Activity is not
* one of the supported Activity types for the test.
*/
public class XrActivityRestrictionRule implements TestRule {
......
......@@ -12,6 +12,7 @@ import android.support.test.InstrumentationRegistry;
import org.junit.Assert;
import org.junit.runner.Description;
import org.chromium.chrome.browser.vr.TestVrShellDelegate;
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;
......@@ -135,6 +136,9 @@ public class HeadTrackingUtils {
*/
public static void checkForAndApplyHeadTrackingModeAnnotation(
VrTestRule rule, Description desc) {
// This is even more broken on standalone devices, and can't be disabled at the shared
// preference level, so no-op here.
if (TestVrShellDelegate.isOnStandalone()) return;
// Check if the test has a HeadTrackingMode annotation
HeadTrackingMode annotation = desc.getAnnotation(HeadTrackingMode.class);
if (annotation == null) return;
......
......@@ -4,17 +4,24 @@
package org.chromium.chrome.browser.vr.util;
import android.content.Intent;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import org.junit.Assert;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.chrome.browser.vr.TestVrShellDelegate;
import org.chromium.chrome.browser.vr.VrFeedbackStatus;
import org.chromium.chrome.browser.vr.VrIntentUtils;
import org.chromium.chrome.browser.vr.rules.ChromeTabbedActivityVrTestRule;
import org.chromium.chrome.browser.vr.rules.CustomTabActivityVrTestRule;
import org.chromium.chrome.browser.vr.rules.VrActivityRestrictionRule;
import org.chromium.chrome.browser.vr.rules.VrTestRule;
import org.chromium.chrome.browser.vr.rules.WebappActivityVrTestRule;
......@@ -114,6 +121,22 @@ public class VrTestRuleUtils extends XrTestRuleUtils {
return parameters;
}
/**
* Creates a RuleChain that applies the XrActivityRestrictionRule and VrActivityRestrictionRule
* before the given VrTestRule.
*
* @param rule The TestRule to wrap in an XrActivityRestrictionRule and
* VrActivityRestrictionRule.
* @return A RuleChain that ensures an XrActivityRestrictionRule and VrActivityRestrictionRule
* is applied before the provided TestRule.
*/
public static RuleChain wrapRuleInActivityRestrictionRule(TestRule rule) {
Assert.assertTrue("Given rule is not an VrTestRule", rule instanceof VrTestRule);
return RuleChain
.outerRule(new VrActivityRestrictionRule(((VrTestRule) rule).getRestriction()))
.around(XrTestRuleUtils.wrapRuleInActivityRestrictionRule(rule));
}
/**
* Ensures that no VR-related activity is currently being displayed. This is meant to be used
* by TestRules before starting any activities. Having a VR activity in the foreground (e.g.
......@@ -123,6 +146,8 @@ public class VrTestRuleUtils extends XrTestRuleUtils {
* for other reasons as well.
*/
public static void ensureNoVrActivitiesDisplayed() {
// This will always be hit on standalones, but we're expected to be in VR in that case.
if (TestVrShellDelegate.isOnStandalone()) return;
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
String currentPackageName = uiDevice.getCurrentPackageName();
if (currentPackageName != null && currentPackageName.contains("vr")) {
......@@ -132,4 +157,18 @@ public class VrTestRuleUtils extends XrTestRuleUtils {
SystemClock.sleep(VRCORE_UNREGISTER_DELAY_MS);
}
}
/**
* Helper method to add additional data to a Chrome startup intent that makes it usable on a
* standalone VR device.
*/
public static Intent maybeAddStandaloneIntentData(Intent intent) {
if (TestVrShellDelegate.isOnStandalone()) {
// Tell VrShellDelegate that it should create a TestVrShellDelegate on startup
TestVrShellDelegate.enableTestVrShellDelegateOnStartupForTesting();
intent.addCategory(VrIntentUtils.DAYDREAM_CATEGORY);
intent.putExtra("android.intent.extra.VR_LAUNCH", true);
}
return intent;
}
}
......@@ -70,7 +70,7 @@ public class XrTestRuleUtils {
* @return A RuleChain that ensures an XrActivityRestrictionRule is applied before the provided
* TestRule.
*/
public static RuleChain wrapRuleInXrActivityRestrictionRule(TestRule rule) {
public static RuleChain wrapRuleInActivityRestrictionRule(TestRule rule) {
Assert.assertTrue("Given rule is not an XrTestRule", rule instanceof XrTestRule);
return RuleChain
.outerRule(new XrActivityRestrictionRule(((XrTestRule) rule).getRestriction()))
......
......@@ -101,7 +101,7 @@ failure, which the test runner treats as a signal that the test was skipped.
#### RuleChain
Every place where `XrActivityRestrictionRule` is used makes use of a `RuleChain`
and `XrTestRuleUtils.wrapRuleInXrActivityRestrictionRule()`. The reason for this
and `XrTestRuleUtils.wrapRuleInActivityRestrictionRule()`. The reason for this
is simply optimization. By using a `RuleChain` to wrap a given
`ChromeActivityTestRule` in an `XrActivityRestrictionRule`, we can ensure that
the decision to skip a test due to being unsupported in an activity is made
......
......@@ -5,6 +5,7 @@
package org.chromium.chrome.test;
import android.content.Context;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.text.TextUtils;
......@@ -83,6 +84,10 @@ public class ChromeJUnit4ClassRunner extends ContentJUnit4ClassRunner {
}
}
private boolean isOnStandaloneVrDevice() {
return Build.DEVICE.equals("vega");
}
private boolean isVrSettingsServiceEnabled() {
// We can't directly check whether the VR settings service is enabled since we don't
// have permission to read the VrCore settings file. Instead, pass a flag.
......@@ -122,7 +127,7 @@ public class ChromeJUnit4ClassRunner extends ContentJUnit4ClassRunner {
boolean daydreamViewPaired = isDaydreamViewPaired();
if (TextUtils.equals(
restriction, ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM)
&& !daydreamViewPaired) {
&& (!daydreamViewPaired || isOnStandaloneVrDevice())) {
return true;
} else if (TextUtils.equals(restriction,
ChromeRestriction.RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
......@@ -130,6 +135,17 @@ public class ChromeJUnit4ClassRunner extends ContentJUnit4ClassRunner {
return true;
}
}
if (TextUtils.equals(restriction, ChromeRestriction.RESTRICTION_TYPE_STANDALONE)) {
return !isOnStandaloneVrDevice();
}
if (TextUtils.equals(restriction,
ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)) {
// Standalone devices are considered to have Daydream View paired.
return !isDaydreamViewPaired();
}
if (TextUtils.equals(restriction, ChromeRestriction.RESTRICTION_TYPE_SVR)) {
return isOnStandaloneVrDevice();
}
if (TextUtils.equals(
restriction, ChromeRestriction.RESTRICTION_TYPE_VR_SETTINGS_SERVICE)) {
return !isVrSettingsServiceEnabled();
......
......@@ -21,6 +21,15 @@ public final class ChromeRestriction {
public static final String RESTRICTION_TYPE_VIEWER_DAYDREAM = "Daydream_View";
/** Specifies the test is only valid if the current VR viewer is not Daydream View */
public static final String RESTRICTION_TYPE_VIEWER_NON_DAYDREAM = "Non_Daydream_View";
/** Specifies the test is only valid if run on a standalone VR device */
public static final String RESTRICTION_TYPE_STANDALONE = "Standalone_VR";
/** Specifies the test is valid if run on either a standalone VR device or a smartphone with
* Daydream View paired. */
public static final String RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE =
"Daydream_View_Or_Standalone_VR";
/** Specifies the test is valid only if run via SVR (smartphone VR), i.e. not on a standalone
* VR device. */
public static final String RESTRICTION_TYPE_SVR = "Smartphone_VR";
/** Specifies the test is only valid if the VR settings service is enabled */
public static final String RESTRICTION_TYPE_VR_SETTINGS_SERVICE = "VR_Settings_Service";
}
......@@ -28,6 +28,7 @@ Tests that the reported device capabilities match expectations.
"bullhead": android_expectation, // Nexus 5X
"hammerhead": android_expectation, // Nexus 5
"marlin": android_expectation, // Pixel XL
"vega": android_expectation, // Lenovo Mirage Solo
"sailfish": android_expectation, // Pixel
"taimen": android_expectation, // Pixel 2 XL
"walleye": android_expectation, // Pixel 2
......
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