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