1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.chrome.browser.vr.util;
6 
7 import android.graphics.PointF;
8 import android.view.Choreographer;
9 import android.view.View;
10 import android.view.ViewGroup;
11 
12 import androidx.annotation.IntDef;
13 
14 import org.junit.Assert;
15 
16 import org.chromium.base.task.PostTask;
17 import org.chromium.base.test.util.CriteriaHelper;
18 import org.chromium.chrome.R;
19 import org.chromium.chrome.browser.app.ChromeActivity;
20 import org.chromium.chrome.browser.vr.KeyboardTestAction;
21 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
22 import org.chromium.chrome.browser.vr.UiTestOperationResult;
23 import org.chromium.chrome.browser.vr.UiTestOperationType;
24 import org.chromium.chrome.browser.vr.UserFriendlyElementName;
25 import org.chromium.chrome.browser.vr.VrControllerTestAction;
26 import org.chromium.chrome.browser.vr.VrDialog;
27 import org.chromium.chrome.browser.vr.VrShell;
28 import org.chromium.chrome.browser.vr.VrViewContainer;
29 import org.chromium.content_public.browser.UiThreadTaskTraits;
30 import org.chromium.content_public.browser.test.util.TestThreadUtils;
31 
32 import java.io.File;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.util.concurrent.CountDownLatch;
36 
37 /**
38  * Class containing utility functions for interacting with the VR browser native UI, e.g. the
39  * omnibox or back button.
40  */
41 public class NativeUiUtils {
42     @IntDef({ScrollDirection.UP, ScrollDirection.DOWN, ScrollDirection.LEFT, ScrollDirection.RIGHT})
43     @Retention(RetentionPolicy.SOURCE)
44     public @interface ScrollDirection {
45         int UP = 0;
46         int DOWN = 1;
47         int LEFT = 2;
48         int RIGHT = 3;
49     }
50 
51     // How many frames to wait after entering text in the omnibox before we can assume that
52     // suggestions are updated. This should only be used if the workaround of inputting text and
53     // waiting for the suggestion box to appear doesn't work, e.g. if you need to input text, wait
54     // for autocomplete, then input more text before committing. 20 is arbitrary, but stable.
55     public static final int NUM_FRAMES_FOR_SUGGESTION_UPDATE = 20;
56     // Arbitrary number of interpolated steps to perform within a scroll to consistently trigger
57     // either fling or non-fling scrolling.
58     public static final int NUM_STEPS_NON_FLING_SCROLL = 60;
59     public static final int NUM_STEPS_FLING_SCROLL = 6;
60     // Number of frames to wait after queueing a non-fling scroll before we can be sure that all the
61     // scroll actions have been processed. The +2 comes from scrolls always having a touch down and
62     // up action with NUM_STEPS_*_SCROLL additional actions in between.
63     public static final int NUM_FRAMES_NON_FLING_SCROLL = NUM_STEPS_NON_FLING_SCROLL + 2;
64     // Arbitrary number of frames to wait before sending a touch up event in order to ensure that a
65     // fast scroll does not become a fling scroll.
66     public static final int NUM_FRAMES_DELAY_TO_PREVENT_FLING = 30;
67     public static final String FRAME_BUFFER_SUFFIX_WEB_XR_OVERLAY = "_WebXrOverlay";
68     public static final String FRAME_BUFFER_SUFFIX_WEB_XR_CONTENT = "_WebXrContent";
69     public static final String FRAME_BUFFER_SUFFIX_BROWSER_UI = "_BrowserUi";
70     public static final String FRAME_BUFFER_SUFFIX_BROWSER_CONTENT = "_BrowserContent";
71     // Valid position to click on the content quad in order to select the reposition bar.
72     public static final PointF REPOSITION_BAR_COORDINATES = new PointF(0.0f, 0.55f);
73 
74     // Arbitrary but reasonable amount of time to expect the UI to stop updating after interacting
75     // with an element.
76     private static final int DEFAULT_UI_QUIESCENCE_TIMEOUT_MS = 2000;
77 
78     /**
79      * Enables the use of both the mock head pose (locked forward) and Chrome-side mocked controller
80      * without performing any specific actions.
81      */
enableMockedInput()82     public static void enableMockedInput() {
83         TestVrShellDelegate.getInstance().performControllerActionForTesting(
84                 0 /* elementName, unused */, VrControllerTestAction.ENABLE_MOCKED_INPUT,
85                 new PointF() /* position, unused */);
86     }
87 
88     /**
89      * Enables the mock keyboard without sending any input. Should be called before performing an
90      * action that would trigger the keyboard for the first time to ensure that the mock keyboard
91      * is used instead of the real one.
92      */
enableMockedKeyboard()93     public static void enableMockedKeyboard() {
94         TestVrShellDelegate.getInstance().performKeyboardInputForTesting(
95                 KeyboardTestAction.ENABLE_MOCKED_KEYBOARD, "" /* unused */);
96     }
97 
98     /**
99      * Clicks on a UI element as if done via a controller.
100      *
101      * @param elementName The UserFriendlyElementName that will be clicked on.
102      * @param position A PointF specifying where on the element to send the click relative to a
103      *        unit square centered at (0, 0).
104      */
clickElement(int elementName, PointF position)105     public static void clickElement(int elementName, PointF position) {
106         clickDown(elementName, position);
107         clickUp(elementName, position);
108     }
109 
110     /**
111      * Moves to the given position in the given element and presses the touchpad down.
112      *
113      * @param elementName The UserFriendlyElementName that will be clicked on.
114      * @param position A PointF specifying where on the element to send the click relative to a
115      *        unit square centered at (0, 0).
116      */
clickDown(int elementName, PointF position)117     public static void clickDown(int elementName, PointF position) {
118         TestVrShellDelegate.getInstance().performControllerActionForTesting(
119                 elementName, VrControllerTestAction.CLICK_DOWN, position);
120     }
121 
122     /**
123      * Moves to the given position in the given element and unpresses the touchpad.
124      *
125      * @param elementName The UserFriendlyElementName that will be unclicked on.
126      * @param position A PointF specifying where on the element to send the click relative to a
127      *        unit square centered at (0, 0).
128      */
clickUp(int elementName, PointF position)129     public static void clickUp(int elementName, PointF position) {
130         TestVrShellDelegate.getInstance().performControllerActionForTesting(
131                 elementName, VrControllerTestAction.CLICK_UP, position);
132     }
133 
134     /**
135      * Hovers over a UI element with the controller.
136      *
137      * @param elementName The UserFriendlyElementName that will be hovered over.
138      * @param position A PointF specifying where on the element to hover relative to a unit square
139      *        centered at (0, 0).
140      */
hoverElement(int elementName, PointF position)141     public static void hoverElement(int elementName, PointF position) {
142         TestVrShellDelegate.getInstance().performControllerActionForTesting(
143                 elementName, VrControllerTestAction.HOVER, position);
144     }
145 
146     /**
147      * Clicks the app button while pointed at a UI element.
148      * @param elementName The UserFriendlyElementName that will be pointed at.
149      * @param position A PointF specifying where on the element to point at relative to a unit
150      *        square centered on (0, 0).
151      */
clickAppButton(int elementName, PointF position)152     public static void clickAppButton(int elementName, PointF position) {
153         TestVrShellDelegate.getInstance().performControllerActionForTesting(
154                 elementName, VrControllerTestAction.APP_DOWN, position);
155         TestVrShellDelegate.getInstance().performControllerActionForTesting(
156                 elementName, VrControllerTestAction.APP_UP, position);
157     }
158 
159     /**
160      * Presses the app button down while pointed at a UI element.
161      * @param elementName The UserFriendlyElementName that will be pointed at.
162      * @param position A PointF specifying where on the element to point at relative to a unit
163      *        square centered on (0, 0).
164      */
pressAppButton(int elementName, PointF position)165     public static void pressAppButton(int elementName, PointF position) {
166         TestVrShellDelegate.getInstance().performControllerActionForTesting(
167                 elementName, VrControllerTestAction.APP_DOWN, position);
168     }
169 
170     /**
171      * Releases the app button while pointed at a UI element.
172      * @param elementName The UserFriendlyElementName that will be pointed at.
173      * @param position A PointF specifying where on the element to point at relative to a unit
174      *        square centered on (0, 0).
175      */
releaseAppButton(int elementName, PointF position)176     public static void releaseAppButton(int elementName, PointF position) {
177         TestVrShellDelegate.getInstance().performControllerActionForTesting(
178                 elementName, VrControllerTestAction.APP_UP, position);
179     }
180 
181     /**
182      * Touches the touchpad at the given coordinates, keeping whatever button states and direction
183      * are already present.
184      *
185      * @param position A PointF specifying where on the touchpad to touch, each axis in the range
186      *        [-1, 1].
187      */
touchDown(PointF position)188     public static void touchDown(PointF position) {
189         TestVrShellDelegate.getInstance().performControllerActionForTesting(
190                 UserFriendlyElementName.NONE /* unused */, VrControllerTestAction.TOUCH_DOWN,
191                 position);
192     }
193 
194     /**
195      * Helper function for performing a non-fling scroll.
196      *
197      * @param direction the ScrollDirection to scroll in.
198      */
scrollNonFling(@crollDirection int direction)199     public static void scrollNonFling(@ScrollDirection int direction) throws InterruptedException {
200         scroll(directionToStartPoint(direction), directionToEndPoint(direction),
201                 NUM_STEPS_NON_FLING_SCROLL, false /* delayTouchUp */);
202     }
203 
204     /**
205      * Helper function for performing a fling scroll.
206      *
207      * @param direction the ScrollDirection to scroll in.
208      */
scrollFling(@crollDirection int direction)209     public static void scrollFling(@ScrollDirection int direction) throws InterruptedException {
210         scroll(directionToStartPoint(direction), directionToEndPoint(direction),
211                 NUM_STEPS_FLING_SCROLL, false /* delayTouchUp */);
212     }
213 
214     /**
215      * Helper function to perform the same action as a fling scroll, but delay the touch up event.
216      * This results in a fast, non-fling scroll that's useful for ensuring that an actual fling
217      * scroll works by asserting the actual fling scroll goes further.
218      *
219      * @param direction the ScrollDirection to scroll in.
220      */
scrollNonFlingFast(@crollDirection int direction)221     public static void scrollNonFlingFast(@ScrollDirection int direction)
222             throws InterruptedException {
223         scroll(directionToStartPoint(direction), directionToEndPoint(direction),
224                 NUM_STEPS_FLING_SCROLL, true /* delayTouchUp */);
225     }
226 
227     /**
228      * Perform a touchpad drag to scroll.
229      *
230      * @param start the position on the touchpad to start the drag.
231      * @param end the position on the touchpad to end the drag.
232      * @param numSteps the number of steps to interpolate between the two points, one step per
233      *        frame.
234      * @param delayTouchUp whether to significantly delay the final touch up event, which should
235      *        prevent fling scrolls regardless of scroll speed.
236      */
scroll(PointF start, PointF end, int numSteps, boolean delayTouchUp)237     public static void scroll(PointF start, PointF end, int numSteps, boolean delayTouchUp)
238             throws InterruptedException {
239         PointF stepIncrement =
240                 new PointF((end.x - start.x) / numSteps, (end.y - start.y) / numSteps);
241         PointF currentPosition = new PointF(start.x, start.y);
242         touchDown(currentPosition);
243         for (int i = 0; i < numSteps; ++i) {
244             currentPosition.offset(stepIncrement.x, stepIncrement.y);
245             touchDown(currentPosition);
246         }
247         if (delayTouchUp) {
248             waitNumFrames(NUM_FRAMES_DELAY_TO_PREVENT_FLING);
249         }
250         TestVrShellDelegate.getInstance().performControllerActionForTesting(
251                 UserFriendlyElementName.NONE /* unused */, VrControllerTestAction.TOUCH_UP, end);
252     }
253 
254     /**
255      * Inputs the given text as if done via the VR keyboard.
256      *
257      * @param inputString The String to input via the keyboard.
258      */
inputString(String inputString)259     public static void inputString(String inputString) {
260         TestVrShellDelegate.getInstance().performKeyboardInputForTesting(
261                 KeyboardTestAction.INPUT_TEXT, inputString);
262     }
263 
264     /**
265      * Presses backspace as if done via the VR keyboard.
266      */
inputBackspace()267     public static void inputBackspace() {
268         TestVrShellDelegate.getInstance().performKeyboardInputForTesting(
269                 KeyboardTestAction.BACKSPACE, "" /* unused */);
270     }
271 
272     /**
273      * Presses enter as if done via the VR keyboard.
274      */
inputEnter()275     public static void inputEnter() throws InterruptedException {
276         TestVrShellDelegate.getInstance().performKeyboardInputForTesting(
277                 KeyboardTestAction.ENTER, "" /* unused */);
278     }
279 
280     /**
281      * Clicks on a UI element as if done via a controller and waits until all resulting
282      * animations have finished and propogated to the point of being visible in screenshots.
283      *
284      * @param elementName The UserFriendlyElementName that will be clicked on.
285      * @param position A PointF specifying where on the element to send the click relative to a
286      *        unit square centered at (0, 0).
287      */
clickElementAndWaitForUiQuiescence( final int elementName, final PointF position)288     public static void clickElementAndWaitForUiQuiescence(
289             final int elementName, final PointF position) throws InterruptedException {
290         performActionAndWaitForUiQuiescence(() -> { clickElement(elementName, position); });
291     }
292 
293     /**
294      * Clicks on a fallback UI element's positive button, e.g. "Allow" or "Confirm".
295      */
clickFallbackUiPositiveButton()296     public static void clickFallbackUiPositiveButton() throws InterruptedException {
297         clickFallbackUiButton(R.id.positive_button);
298     }
299 
300     /**
301      * Clicks on a fallback UI element's negative button, e.g. "Deny" or "Cancel".
302      */
clickFallbackUiNegativeButton()303     public static void clickFallbackUiNegativeButton() throws InterruptedException {
304         clickFallbackUiButton(R.id.negative_button);
305     }
306 
307     /**
308      * Clicks and drags within a single UI element.
309      *
310      * @param elementName The UserFriendlyElementName that will be clicked and dragged in.
311      * @param positionStart The PointF specifying where on the element to start the click/drag
312      *        relative to a unit square centered at (0, 0).
313      * @param positionEnd The PointF specifying where on the element to end the click/drag relative
314      *        to a unit square centered at (0, 0);
315      * @param numInterpolatedSteps How many steps to interpolate the drag between the provided
316      *        start and end positions.
317      */
clickAndDragElement( int elementName, PointF positionStart, PointF positionEnd, int numInterpolatedSteps)318     public static void clickAndDragElement(
319             int elementName, PointF positionStart, PointF positionEnd, int numInterpolatedSteps) {
320         Assert.assertTrue(
321                 "Given a negative number of steps to interpolate", numInterpolatedSteps >= 0);
322         TestVrShellDelegate.getInstance().performControllerActionForTesting(
323                 elementName, VrControllerTestAction.CLICK_DOWN, positionStart);
324         PointF stepOffset =
325                 new PointF((positionEnd.x - positionStart.x) / (numInterpolatedSteps + 1),
326                         (positionEnd.y - positionStart.y) / (numInterpolatedSteps + 1));
327         PointF currentPosition = positionStart;
328         for (int i = 0; i < numInterpolatedSteps; i++) {
329             currentPosition.offset(stepOffset.x, stepOffset.y);
330             TestVrShellDelegate.getInstance().performControllerActionForTesting(
331                     elementName, VrControllerTestAction.MOVE, currentPosition);
332         }
333         TestVrShellDelegate.getInstance().performControllerActionForTesting(
334                 elementName, VrControllerTestAction.MOVE, positionEnd);
335         TestVrShellDelegate.getInstance().performControllerActionForTesting(
336                 elementName, VrControllerTestAction.CLICK_UP, positionEnd);
337     }
338 
339     /**
340      * Sets the native code to start using the real controller and head pose data again instead of
341      * fake testing data.
342      */
revertToRealInput()343     public static void revertToRealInput() {
344         TestVrShellDelegate.getInstance().performControllerActionForTesting(
345                 0 /* elementName, unused */, VrControllerTestAction.REVERT_TO_REAL_INPUT,
346                 new PointF() /* position, unused */);
347     }
348 
349     /**
350      * Runs the given Runnable and waits until the native UI reports that it is quiescent. The
351      * provided Runnable is expected to cause a UI change of some sort, so the quiescence wait will
352      * fail if no change is detected within the allotted time.
353      *
354      * @param action A Runnable containing the action to perform.
355      */
performActionAndWaitForUiQuiescence(Runnable action)356     public static void performActionAndWaitForUiQuiescence(Runnable action) {
357         final TestVrShellDelegate instance = TestVrShellDelegate.getInstance();
358         final CountDownLatch resultLatch = new CountDownLatch(1);
359         final VrShell.UiOperationData operationData = new VrShell.UiOperationData();
360         operationData.actionType = UiTestOperationType.UI_ACTIVITY_RESULT;
361         operationData.resultCallback = () -> {
362             resultLatch.countDown();
363         };
364         operationData.timeoutMs = DEFAULT_UI_QUIESCENCE_TIMEOUT_MS;
365         // Run on the UI thread to prevent issues with registering a new callback before
366         // ReportUiOperationResultForTesting has finished.
367         TestThreadUtils.runOnUiThreadBlocking(
368                 () -> { instance.registerUiOperationCallbackForTesting(operationData); });
369         action.run();
370 
371         // Wait for any outstanding animations to finish. Catch the interrupted exception so we
372         // don't have to try/catch anytime we chain multiple actions.
373         try {
374             resultLatch.await();
375         } catch (InterruptedException e) {
376             Assert.fail("Interrupted while waiting for UI quiescence: " + e.toString());
377         }
378         int uiResult =
379                 instance.getLastUiOperationResultForTesting(UiTestOperationType.UI_ACTIVITY_RESULT);
380         Assert.assertEquals("UI reported non-quiescent result '"
381                         + uiTestOperationResultToString(uiResult) + "'",
382                 UiTestOperationResult.QUIESCENT, uiResult);
383     }
384 
385     /**
386      * Waits until either the UI reports quiescence or a timeout is reached. Unlike
387      * performActionAndWaitForUiQuiescence, this does not fail if no UI change is detected within
388      * the allotted time, so it can be used when it is unsure whether the UI is already quiescent
389      * or not, e.g. when initally entering the VR browser.
390      */
waitForUiQuiescence()391     public static void waitForUiQuiescence() {
392         final TestVrShellDelegate instance = TestVrShellDelegate.getInstance();
393         final CountDownLatch resultLatch = new CountDownLatch(1);
394         final VrShell.UiOperationData operationData = new VrShell.UiOperationData();
395         operationData.actionType = UiTestOperationType.UI_ACTIVITY_RESULT;
396         operationData.resultCallback = () -> {
397             resultLatch.countDown();
398         };
399         operationData.timeoutMs = DEFAULT_UI_QUIESCENCE_TIMEOUT_MS;
400         TestThreadUtils.runOnUiThreadBlocking(
401                 () -> { instance.registerUiOperationCallbackForTesting(operationData); });
402         // Catch the interrupted exception so we don't have to try/catch anytime we chain multiple
403         // actions.
404         try {
405             resultLatch.await();
406         } catch (InterruptedException e) {
407             Assert.fail("Interrupted while waiting for UI quiescence: " + e.toString());
408         }
409 
410         int uiResult =
411                 instance.getLastUiOperationResultForTesting(UiTestOperationType.UI_ACTIVITY_RESULT);
412         Assert.assertTrue("UI reported non-quiescent result '"
413                         + uiTestOperationResultToString(uiResult) + "'",
414                 uiResult == UiTestOperationResult.QUIESCENT
415                         || uiResult == UiTestOperationResult.TIMEOUT_NO_START);
416     }
417 
418     /**
419      * Runs the given Runnable and waits until the specified element matches the requested
420      * visibility.
421      *
422      * @param elementName The UserFriendlyElementName to wait on to change visibility.
423      * @param status The visibility status to wait for.
424      * @param action A Runnable containing the action to perform.
425      */
performActionAndWaitForVisibilityStatus( final int elementName, final boolean visible, Runnable action)426     public static void performActionAndWaitForVisibilityStatus(
427             final int elementName, final boolean visible, Runnable action) {
428         final TestVrShellDelegate instance = TestVrShellDelegate.getInstance();
429         final CountDownLatch resultLatch = new CountDownLatch(1);
430         final VrShell.UiOperationData operationData = new VrShell.UiOperationData();
431         operationData.actionType = UiTestOperationType.ELEMENT_VISIBILITY_STATUS;
432         operationData.resultCallback = () -> {
433             resultLatch.countDown();
434         };
435         operationData.timeoutMs = DEFAULT_UI_QUIESCENCE_TIMEOUT_MS;
436         operationData.elementName = elementName;
437         operationData.visibility = visible;
438         // Run on the UI thread to prevent issues with registering a new callback before
439         // ReportUiOperationResultForTesting has finished.
440         TestThreadUtils.runOnUiThreadBlocking(
441                 () -> { instance.registerUiOperationCallbackForTesting(operationData); });
442         action.run();
443 
444         // Wait for the result to be reported. Catch the interrupted exception so we don't have to
445         // try/catch anytime we chain multiple actions.
446         try {
447             resultLatch.await();
448         } catch (InterruptedException e) {
449             Assert.fail("Interrupted while waiting for visibility status: " + e.toString());
450         }
451 
452         int result = instance.getLastUiOperationResultForTesting(
453                 UiTestOperationType.ELEMENT_VISIBILITY_STATUS);
454         Assert.assertEquals("UI reported non-visibility-changed result '"
455                         + uiTestOperationResultToString(result) + "'",
456                 UiTestOperationResult.VISIBILITY_MATCH, result);
457     }
458 
459     /**
460      * Blocks until the specified number of frames have been triggered by the Choreographer.
461      *
462      * @param numFrames The number of frames to wait for.
463      */
waitNumFrames(int numFrames)464     public static void waitNumFrames(int numFrames) {
465         final CountDownLatch frameLatch = new CountDownLatch(numFrames);
466         PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
467             final Choreographer.FrameCallback callback = new Choreographer.FrameCallback() {
468                 @Override
469                 public void doFrame(long frameTimeNanos) {
470                     if (frameLatch.getCount() == 0) return;
471                     Choreographer.getInstance().postFrameCallback(this);
472                     frameLatch.countDown();
473                 }
474             };
475             Choreographer.getInstance().postFrameCallback(callback);
476         });
477         try {
478             frameLatch.await();
479         } catch (InterruptedException e) {
480             Assert.fail("Interrupted while waiting for frames: " + e.toString());
481         }
482     }
483 
484     /**
485      * Tells the native UI to dump the next frame's frame buffers to disk and waits for it to
486      * signal that the dump is complete.
487      *
488      * @param filepathBase The filepath to use as a base for image dumps. Will have a suffix and
489      *        file extension automatically appended.
490      */
dumpNextFramesFrameBuffers(String filepathBase)491     public static void dumpNextFramesFrameBuffers(String filepathBase) throws InterruptedException {
492         // Clear out any existing images with the names of the files that may be created.
493         for (String suffix : new String[] {FRAME_BUFFER_SUFFIX_WEB_XR_OVERLAY,
494                      FRAME_BUFFER_SUFFIX_WEB_XR_CONTENT, FRAME_BUFFER_SUFFIX_BROWSER_UI,
495                      FRAME_BUFFER_SUFFIX_BROWSER_CONTENT}) {
496             File dumpFile = new File(filepathBase, suffix + ".png");
497             Assert.assertFalse("Failed to delete existing screenshot",
498                     dumpFile.exists() && !dumpFile.delete());
499         }
500 
501         final TestVrShellDelegate instance = TestVrShellDelegate.getInstance();
502         final CountDownLatch resultLatch = new CountDownLatch(1);
503         final VrShell.UiOperationData operationData = new VrShell.UiOperationData();
504         operationData.actionType = UiTestOperationType.FRAME_BUFFER_DUMPED;
505         operationData.resultCallback = () -> {
506             resultLatch.countDown();
507         };
508 
509         // Run on the UI thread to prevent issues with registering a new callback before
510         // ReportUiOperationResultForTesting has finished.
511         TestThreadUtils.runOnUiThreadBlocking(
512                 () -> { instance.registerUiOperationCallbackForTesting(operationData); });
513         instance.saveNextFrameBufferToDiskForTesting(filepathBase);
514         resultLatch.await();
515     }
516 
517     /**
518      * Returns the Container of 2D UI that is shown in VR.
519      */
getVrViewContainer()520     public static ViewGroup getVrViewContainer() {
521         VrShell vrShell = TestVrShellDelegate.getVrShellForTesting();
522         return vrShell.getVrViewContainerForTesting();
523     }
524 
525     /**
526      * Waits until a modal dialog is or is not shown.
527      */
waitForModalDialogStatus( final boolean shouldBeShown, final ChromeActivity activity)528     public static void waitForModalDialogStatus(
529             final boolean shouldBeShown, final ChromeActivity activity) {
530         CriteriaHelper.pollUiThread(
531                 ()
532                         -> {
533                     return shouldBeShown == activity.getModalDialogManager().isShowing();
534                 },
535                 "Timed out waiting for modal dialog to "
536                         + (shouldBeShown ? "be shown" : "not be shown"));
537     }
538 
clickFallbackUiButton(int buttonId)539     private static void clickFallbackUiButton(int buttonId) throws InterruptedException {
540         VrShell vrShell = TestVrShellDelegate.getVrShellForTesting();
541         VrViewContainer viewContainer = vrShell.getVrViewContainerForTesting();
542         Assert.assertTrue(
543                 "VrViewContainer does not have children", viewContainer.getChildCount() > 0);
544         // Click on whatever dialog was most recently added
545         VrDialog vrDialog = (VrDialog) viewContainer.getChildAt(viewContainer.getChildCount() - 1);
546         View button = vrDialog.findViewById(buttonId);
547         Assert.assertNotNull("Did not find view with specified ID", button);
548         // Calculate the center of the button we want to click on and scale it to fit a unit square
549         // centered on (0,0).
550         float x = ((button.getX() + button.getWidth() / 2) - vrDialog.getWidth() / 2)
551                 / vrDialog.getWidth();
552         float y = ((button.getY() + button.getHeight() / 2) - vrDialog.getHeight() / 2)
553                 / vrDialog.getHeight();
554         PointF buttonCenter = new PointF(x, y);
555         clickElementAndWaitForUiQuiescence(UserFriendlyElementName.BROWSING_DIALOG, buttonCenter);
556     }
557 
uiTestOperationResultToString(int result)558     private static String uiTestOperationResultToString(int result) {
559         switch (result) {
560             case UiTestOperationResult.UNREPORTED:
561                 return "Unreported";
562             case UiTestOperationResult.QUIESCENT:
563                 return "Quiescent";
564             case UiTestOperationResult.TIMEOUT_NO_START:
565                 return "Timeout (UI activity not started)";
566             case UiTestOperationResult.TIMEOUT_NO_END:
567                 return "Timeout (UI activity not stopped)";
568             case UiTestOperationResult.VISIBILITY_MATCH:
569                 return "Visibility match";
570             case UiTestOperationResult.TIMEOUT_NO_VISIBILITY_MATCH:
571                 return "Timeout (Element visibility did not match)";
572             default:
573                 return "Unknown result";
574         }
575     }
576 
directionToStartPoint(@crollDirection int direction)577     private static PointF directionToStartPoint(@ScrollDirection int direction) {
578         switch (direction) {
579             case ScrollDirection.UP:
580                 return new PointF(0.5f, 0.05f);
581             case ScrollDirection.DOWN:
582                 return new PointF(0.5f, 0.95f);
583             case ScrollDirection.LEFT:
584                 return new PointF(0.05f, 0.5f);
585             default:
586                 return new PointF(0.95f, 0.5f);
587         }
588     }
589 
directionToEndPoint(@crollDirection int direction)590     private static PointF directionToEndPoint(@ScrollDirection int direction) {
591         switch (direction) {
592             case ScrollDirection.UP:
593                 return new PointF(0.5f, 0.95f);
594             case ScrollDirection.DOWN:
595                 return new PointF(0.5f, 0.05f);
596             case ScrollDirection.LEFT:
597                 return new PointF(0.95f, 0.5f);
598             default:
599                 return new PointF(0.05f, 0.5f);
600         }
601     }
602 }
603