1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.rotary; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; 21 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; 22 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED; 23 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; 24 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; 25 import static android.view.accessibility.AccessibilityWindowInfo.TYPE_APPLICATION; 26 27 import static com.android.car.ui.utils.DirectManipulationHelper.DIRECT_MANIPULATION; 28 import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS; 29 30 import static com.google.common.truth.Truth.assertThat; 31 32 import static org.mockito.ArgumentMatchers.any; 33 import static org.mockito.ArgumentMatchers.anyList; 34 import static org.mockito.Mockito.doAnswer; 35 import static org.mockito.Mockito.mock; 36 import static org.mockito.Mockito.times; 37 import static org.mockito.Mockito.verify; 38 import static org.mockito.Mockito.when; 39 import static org.testng.AssertJUnit.assertNull; 40 41 import android.accessibilityservice.AccessibilityServiceInfo; 42 import android.app.Activity; 43 import android.app.UiAutomation; 44 import android.car.CarOccupantZoneManager; 45 import android.car.input.CarInputManager; 46 import android.car.input.RotaryEvent; 47 import android.content.ComponentName; 48 import android.content.Intent; 49 import android.hardware.input.InputManager; 50 import android.view.KeyEvent; 51 import android.view.View; 52 import android.view.accessibility.AccessibilityEvent; 53 import android.view.accessibility.AccessibilityNodeInfo; 54 import android.view.accessibility.AccessibilityWindowInfo; 55 import android.widget.Button; 56 57 import androidx.annotation.LayoutRes; 58 import androidx.test.ext.junit.runners.AndroidJUnit4; 59 import androidx.test.platform.app.InstrumentationRegistry; 60 import androidx.test.rule.ActivityTestRule; 61 62 import com.android.car.ui.FocusParkingView; 63 import com.android.car.ui.utils.DirectManipulationHelper; 64 65 import org.junit.After; 66 import org.junit.AfterClass; 67 import org.junit.Before; 68 import org.junit.BeforeClass; 69 import org.junit.Test; 70 import org.junit.runner.RunWith; 71 import org.mockito.MockitoAnnotations; 72 import org.mockito.Spy; 73 74 import java.util.ArrayList; 75 import java.util.Collections; 76 import java.util.List; 77 78 @RunWith(AndroidJUnit4.class) 79 public class RotaryServiceTest { 80 81 private final static String HOST_APP_PACKAGE_NAME = "host.app.package.name"; 82 private final static String CLIENT_APP_PACKAGE_NAME = "client.app.package.name"; 83 private static final int ROTATION_ACCELERATION_2X_MS = 50; 84 private static final int ROTATION_ACCELERATION_3X_MS = 25; 85 86 private static UiAutomation sUiAutomation; 87 private static int sOriginalFlags; 88 89 private final List<AccessibilityNodeInfo> mNodes = new ArrayList<>(); 90 91 private AccessibilityNodeInfo mWindowRoot; 92 private ActivityTestRule<NavigatorTestActivity> mActivityRule; 93 private Intent mIntent; 94 private NodeBuilder mNodeBuilder; 95 96 private @Spy 97 RotaryService mRotaryService; 98 private @Spy 99 Navigator mNavigator; 100 101 @BeforeClass setUpClass()102 public static void setUpClass() { 103 sUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 104 105 // FLAG_RETRIEVE_INTERACTIVE_WINDOWS is necessary to reliably access the root window. 106 AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo(); 107 sOriginalFlags = serviceInfo.flags; 108 serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; 109 sUiAutomation.setServiceInfo(serviceInfo); 110 } 111 112 @AfterClass tearDownClass()113 public static void tearDownClass() { 114 AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo(); 115 serviceInfo.flags = sOriginalFlags; 116 sUiAutomation.setServiceInfo(serviceInfo); 117 118 } 119 120 @Before setUp()121 public void setUp() { 122 mActivityRule = new ActivityTestRule<>(NavigatorTestActivity.class); 123 mIntent = new Intent(); 124 mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 125 126 MockitoAnnotations.initMocks(this); 127 mRotaryService.setNavigator(mNavigator); 128 mRotaryService.setNodeCopier(MockNodeCopierProvider.get()); 129 mRotaryService.setInputManager(mock(InputManager.class)); 130 mRotaryService.setRotateAcceleration(ROTATION_ACCELERATION_2X_MS, 131 ROTATION_ACCELERATION_3X_MS); 132 mNodeBuilder = new NodeBuilder(new ArrayList<>()); 133 } 134 135 @After tearDown()136 public void tearDown() { 137 mActivityRule.finishActivity(); 138 Utils.recycleNode(mWindowRoot); 139 Utils.recycleNodes(mNodes); 140 } 141 142 /** 143 * Tests {@link RotaryService#initFocus()} in the following view tree: 144 * <pre> 145 * root 146 * / \ 147 * / \ 148 * focusParkingView focusArea 149 * / | \ 150 * / | \ 151 * button1 defaultFocus button3 152 * (focused) 153 * </pre> 154 * and {@link RotaryService#mFocusedNode} is already set to defaultFocus. 155 */ 156 @Test testInitFocus_alreadyInitialized()157 public void testInitFocus_alreadyInitialized() { 158 initActivity(R.layout.rotary_service_test_1_activity); 159 160 AccessibilityWindowInfo window = new WindowBuilder() 161 .setRoot(mWindowRoot) 162 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 163 .build(); 164 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 165 when(mRotaryService.getWindows()).thenReturn(windows); 166 167 AccessibilityNodeInfo defaultFocusNode = createNode("defaultFocus"); 168 assertThat(defaultFocusNode.isFocused()).isTrue(); 169 mRotaryService.setFocusedNode(defaultFocusNode); 170 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 171 172 boolean consumed = mRotaryService.initFocus(); 173 assertThat(consumed).isFalse(); 174 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 175 } 176 177 /** 178 * Tests {@link RotaryService#initFocus()} in the following view tree: 179 * <pre> 180 * root 181 * / \ 182 * / \ 183 * focusParkingView focusArea 184 * / | \ 185 * / | \ 186 * button1 defaultFocus button3 187 * (focused) 188 * </pre> 189 * {@link RotaryService#mFocusedNode} is not initialized, 190 * and {@link RotaryService#mInRotaryMode} is set to true. 191 */ 192 @Test testInitFocus_focusOnAlreadyFocusedView()193 public void testInitFocus_focusOnAlreadyFocusedView() { 194 initActivity(R.layout.rotary_service_test_1_activity); 195 196 AccessibilityWindowInfo window = new WindowBuilder() 197 .setRoot(mWindowRoot) 198 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 199 .build(); 200 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 201 when(mRotaryService.getWindows()).thenReturn(windows); 202 203 Activity activity = mActivityRule.getActivity(); 204 Button button3 = activity.findViewById(R.id.button3); 205 button3.post(() -> button3.requestFocus()); 206 // TODO(b/246423854): Find out why we need to setInRotaryMode(true) explicitly 207 mRotaryService.setInRotaryMode(true); 208 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 209 assertThat(button3.isFocused()).isTrue(); 210 assertNull(mRotaryService.getFocusedNode()); 211 212 boolean consumed = mRotaryService.initFocus(); 213 AccessibilityNodeInfo button3Node = createNode("button3"); 214 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button3Node); 215 assertThat(consumed).isFalse(); 216 } 217 218 /** 219 * Tests {@link RotaryService#initFocus()} in the following view tree: 220 * <pre> 221 * root 222 * / \ 223 * / \ 224 * focusParkingView focusArea 225 * (focused) / | \ 226 * / | \ 227 * button1 defaultFocus button3 228 * </pre> 229 * and {@link RotaryService#mFocusedNode} is null. 230 */ 231 @Test testInitFocus_focusOnDefaultFocusView()232 public void testInitFocus_focusOnDefaultFocusView() { 233 initActivity(R.layout.rotary_service_test_1_activity); 234 235 AccessibilityWindowInfo window = new WindowBuilder() 236 .setRoot(mWindowRoot) 237 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 238 .build(); 239 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 240 when(mRotaryService.getWindows()).thenReturn(windows); 241 when(mRotaryService.getRootInActiveWindow()) 242 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 243 244 // Move focus to the FocusParkingView. 245 Activity activity = mActivityRule.getActivity(); 246 FocusParkingView fpv = activity.findViewById(R.id.focusParkingView); 247 fpv.setShouldRestoreFocus(false); 248 fpv.post(() -> fpv.requestFocus()); 249 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 250 assertThat(fpv.isFocused()).isTrue(); 251 assertNull(mRotaryService.getFocusedNode()); 252 253 boolean consumed = mRotaryService.initFocus(); 254 AccessibilityNodeInfo defaultFocusNode = createNode("defaultFocus"); 255 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 256 assertThat(consumed).isTrue(); 257 } 258 259 /** 260 * Tests {@link RotaryService#initFocus()} in the following view tree: 261 * <pre> 262 * root 263 * / \ 264 * / \ 265 * focusParkingView focusArea 266 * (focused) / | \ 267 * / | \ 268 * button1 defaultFocus button3 269 * (disabled) (last touched) 270 * </pre> 271 * and {@link RotaryService#mFocusedNode} is null. 272 */ 273 @Test testInitFocus_focusOnLastTouchedView()274 public void testInitFocus_focusOnLastTouchedView() { 275 initActivity(R.layout.rotary_service_test_1_activity); 276 277 AccessibilityWindowInfo window = new WindowBuilder() 278 .setRoot(mWindowRoot) 279 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 280 .build(); 281 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 282 when(mRotaryService.getWindows()).thenReturn(windows); 283 when(mRotaryService.getRootInActiveWindow()) 284 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 285 286 // The user touches button3. In reality it should enter touch mode therefore no view will 287 // be focused. To emulate this case, this test just moves focus to the FocusParkingView 288 // and sets last touched node to button3. 289 Activity activity = mActivityRule.getActivity(); 290 FocusParkingView fpv = activity.findViewById(R.id.focusParkingView); 291 fpv.setShouldRestoreFocus(false); 292 fpv.post(fpv::requestFocus); 293 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 294 assertThat(fpv.isFocused()).isTrue(); 295 AccessibilityNodeInfo button3Node = createNode("button3"); 296 mRotaryService.setLastTouchedNode(button3Node); 297 assertNull(mRotaryService.getFocusedNode()); 298 299 boolean consumed = mRotaryService.initFocus(); 300 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button3Node); 301 assertThat(consumed).isTrue(); 302 } 303 304 /** 305 * Tests {@link RotaryService#initFocus()} in the following view tree: 306 * <pre> 307 * root 308 * / \ 309 * / \ 310 * focusParkingView focusArea 311 * (focused) / | \ 312 * / | \ 313 * button1 defaultFocus button3 314 * (disabled) 315 * </pre> 316 * and {@link RotaryService#mFocusedNode} is null. 317 */ 318 @Test testInitFocus_focusOnFirstFocusableView()319 public void testInitFocus_focusOnFirstFocusableView() { 320 initActivity(R.layout.rotary_service_test_1_activity); 321 322 AccessibilityWindowInfo window = new WindowBuilder() 323 .setRoot(mWindowRoot) 324 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 325 .build(); 326 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 327 when(mRotaryService.getWindows()).thenReturn(windows); 328 when(mRotaryService.getRootInActiveWindow()) 329 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 330 331 // Move focus to the FocusParkingView and disable the default focus view. 332 Activity activity = mActivityRule.getActivity(); 333 FocusParkingView fpv = activity.findViewById(R.id.focusParkingView); 334 Button defaultFocus = activity.findViewById(R.id.defaultFocus); 335 fpv.setShouldRestoreFocus(false); 336 fpv.post(() -> { 337 fpv.requestFocus(); 338 defaultFocus.setEnabled(false); 339 340 }); 341 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 342 assertThat(fpv.isFocused()).isTrue(); 343 assertThat(defaultFocus.isEnabled()).isFalse(); 344 assertNull(mRotaryService.getFocusedNode()); 345 346 boolean consumed = mRotaryService.initFocus(); 347 AccessibilityNodeInfo button1Node = createNode("button1"); 348 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button1Node); 349 assertThat(consumed).isTrue(); 350 } 351 352 /** 353 * Tests {@link RotaryService#initFocus()} in the following node tree: 354 * <pre> 355 * clientAppRoot 356 * / \ 357 * / \ 358 * button1 surfaceView(focused) 359 * | 360 * hostAppRoot 361 * / \ 362 * / \ 363 * focusParkingView button2(focused) 364 * </pre> 365 * {@link RotaryService#mFocusedNode} is null, 366 * and {@link RotaryService#mInRotaryMode} is set to true. 367 */ 368 @Test testInitFocus_focusOnHostNode()369 public void testInitFocus_focusOnHostNode() { 370 initActivity(R.layout.rotary_service_test_1_activity); 371 372 mNavigator.addClientApp(CLIENT_APP_PACKAGE_NAME); 373 mNavigator.mSurfaceViewHelper.mHostApp = HOST_APP_PACKAGE_NAME; 374 375 AccessibilityNodeInfo clientAppRoot = mNodeBuilder 376 .setPackageName(CLIENT_APP_PACKAGE_NAME) 377 .build(); 378 AccessibilityNodeInfo button1 = mNodeBuilder 379 .setParent(clientAppRoot) 380 .setPackageName(CLIENT_APP_PACKAGE_NAME) 381 .build(); 382 AccessibilityNodeInfo surfaceView = mNodeBuilder 383 .setParent(clientAppRoot) 384 .setFocused(true) 385 .setPackageName(CLIENT_APP_PACKAGE_NAME) 386 .setClassName(Utils.SURFACE_VIEW_CLASS_NAME) 387 .build(); 388 389 AccessibilityNodeInfo hostAppRoot = mNodeBuilder 390 .setParent(surfaceView) 391 .setPackageName(HOST_APP_PACKAGE_NAME) 392 .build(); 393 AccessibilityNodeInfo focusParkingView = mNodeBuilder 394 .setParent(hostAppRoot) 395 .setPackageName(HOST_APP_PACKAGE_NAME) 396 .setFpv() 397 .build(); 398 AccessibilityNodeInfo button2 = mNodeBuilder 399 .setParent(hostAppRoot) 400 .setFocused(true) 401 .setPackageName(HOST_APP_PACKAGE_NAME) 402 .build(); 403 404 AccessibilityWindowInfo window = new WindowBuilder().setRoot(clientAppRoot).build(); 405 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 406 when(mRotaryService.getWindows()).thenReturn(windows); 407 408 // TODO(b/246423854): Find out why we need to setInRotaryMode(true) explicitly 409 mRotaryService.setInRotaryMode(true); 410 boolean consumed = mRotaryService.initFocus(); 411 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button2); 412 assertThat(consumed).isFalse(); 413 } 414 415 /** 416 * Tests {@link RotaryService#onRotaryEvents} in the following view tree: 417 * <pre> 418 * root 419 * / \ 420 * / \ 421 * focusParkingView focusArea 422 * (focused) / | \ 423 * / | \ 424 * button1 defaultFocus button3 425 * </pre> 426 */ 427 @Test testOnRotaryEvents_withoutFocusedView()428 public void testOnRotaryEvents_withoutFocusedView() { 429 initActivity(R.layout.rotary_service_test_1_activity); 430 431 AccessibilityWindowInfo window = new WindowBuilder() 432 .setRoot(mWindowRoot) 433 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 434 .build(); 435 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 436 when(mRotaryService.getWindows()).thenReturn(windows); 437 when(mRotaryService.getRootInActiveWindow()) 438 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 439 440 // Move focus to the FocusParkingView. 441 Activity activity = mActivityRule.getActivity(); 442 FocusParkingView fpv = activity.findViewById(R.id.focusParkingView); 443 fpv.setShouldRestoreFocus(false); 444 fpv.post(() -> fpv.requestFocus()); 445 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 446 assertThat(fpv.isFocused()).isTrue(); 447 assertNull(mRotaryService.getFocusedNode()); 448 449 // Since there is no non-FocusParkingView focused, rotating the controller should 450 // initialize the focus. 451 452 int inputType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION; 453 boolean clockwise = true; 454 long[] timestamps = new long[]{0}; 455 RotaryEvent rotaryEvent = new RotaryEvent(inputType, clockwise, timestamps); 456 List<RotaryEvent> events = Collections.singletonList(rotaryEvent); 457 458 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 459 mRotaryService.onRotaryEvents(validDisplayId, events); 460 461 AccessibilityNodeInfo defaultFocusNode = createNode("defaultFocus"); 462 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 463 } 464 465 /** 466 * Tests {@link RotaryService#onRotaryEvents} in the following view tree: 467 * <pre> 468 * root 469 * / \ 470 * / \ 471 * focusParkingView focusArea 472 * / | \ 473 * / | \ 474 * button1 defaultFocus button3 475 * (focused) 476 * </pre> 477 */ 478 @Test testOnRotaryEvents_withFocusedView()479 public void testOnRotaryEvents_withFocusedView() { 480 initActivity(R.layout.rotary_service_test_1_activity); 481 482 AccessibilityWindowInfo window = new WindowBuilder() 483 .setRoot(mWindowRoot) 484 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 485 .build(); 486 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 487 when(mRotaryService.getWindows()).thenReturn(windows); 488 doAnswer(invocation -> 1) 489 .when(mRotaryService).getRotateAcceleration(any(Integer.class), any(Long.class)); 490 491 AccessibilityNodeInfo defaultFocusNode = createNode("defaultFocus"); 492 assertThat(defaultFocusNode.isFocused()).isTrue(); 493 mRotaryService.setFocusedNode(defaultFocusNode); 494 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 495 496 // Since RotaryService#mFocusedNode is already initialized, rotating the controller 497 // clockwise should move the focus from defaultFocus to button3. 498 499 int inputType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION; 500 boolean clockwise = true; 501 long[] timestamps = new long[]{0}; 502 RotaryEvent rotaryEvent = new RotaryEvent(inputType, clockwise, timestamps); 503 List<RotaryEvent> events = Collections.singletonList(rotaryEvent); 504 505 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 506 mRotaryService.onRotaryEvents(validDisplayId, events); 507 508 AccessibilityNodeInfo button3Node = createNode("button3"); 509 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button3Node); 510 511 // Rotating the controller clockwise again should do nothing because button3 is the last 512 // child of its ancestor FocusArea and the ancestor FocusArea doesn't support wrap-around. 513 mRotaryService.onRotaryEvents(validDisplayId, events); 514 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button3Node); 515 516 // Rotating the controller counterclockwise should move focus to defaultFocus. 517 clockwise = false; 518 rotaryEvent = new RotaryEvent(inputType, clockwise, timestamps); 519 events = Collections.singletonList(rotaryEvent); 520 mRotaryService.onRotaryEvents(validDisplayId, events); 521 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 522 } 523 524 /** 525 * Tests {@link RotaryService#onRotaryEvents} in the following view tree: 526 * <pre> 527 * root 528 * / \ 529 * / \ 530 * focusParkingView focusArea 531 * / | | \ 532 * / | | \ 533 * defaultFocus button2 button3 ... button6 534 * (focused) 535 * </pre> 536 */ 537 @Test testOnRotaryEvents_acceleration()538 public void testOnRotaryEvents_acceleration() { 539 initActivity(R.layout.rotary_service_test_3_activity); 540 541 AccessibilityWindowInfo window = new WindowBuilder() 542 .setRoot(mWindowRoot) 543 .setBoundsInScreen(mWindowRoot.getBoundsInScreen()) 544 .build(); 545 List<AccessibilityWindowInfo> windows = Collections.singletonList(window); 546 when(mRotaryService.getWindows()).thenReturn(windows); 547 548 AccessibilityNodeInfo defaultFocusNode = createNode("defaultFocus"); 549 assertThat(defaultFocusNode.isFocused()).isTrue(); 550 mRotaryService.setFocusedNode(defaultFocusNode); 551 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 552 553 554 // Rotating the controller clockwise slowly should move the focus from defaultFocus to 555 // button2. 556 int inputType = CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION; 557 int eventTime = ROTATION_ACCELERATION_2X_MS + 1; 558 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 559 mRotaryService.onRotaryEvents(validDisplayId, 560 Collections.singletonList( 561 new RotaryEvent(inputType, true, new long[]{eventTime}))); 562 AccessibilityNodeInfo button2Node = createNode("button2"); 563 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button2Node); 564 565 // Move focus back to defaultFocus. 566 eventTime += ROTATION_ACCELERATION_2X_MS + 1; 567 mRotaryService.onRotaryEvents(validDisplayId, 568 Collections.singletonList( 569 new RotaryEvent(inputType, false, new long[]{eventTime}))); 570 assertThat(mRotaryService.getFocusedNode()).isEqualTo(defaultFocusNode); 571 572 // Rotating the controller clockwise somewhat fast should move the focus from defaultFocus 573 // to button3. 574 eventTime += ROTATION_ACCELERATION_2X_MS; 575 mRotaryService.onRotaryEvents(validDisplayId, 576 Collections.singletonList( 577 new RotaryEvent(inputType, true, new long[]{eventTime}))); 578 AccessibilityNodeInfo button3Node = createNode("button3"); 579 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button3Node); 580 581 // Move focus back to defaultFocus. 582 eventTime += ROTATION_ACCELERATION_2X_MS; 583 mRotaryService.onRotaryEvents(validDisplayId, 584 Collections.singletonList( 585 new RotaryEvent(inputType, false, new long[]{eventTime}))); 586 587 // Rotating the controller clockwise very faster should move the focus from defaultFocus to 588 // button4. 589 eventTime += ROTATION_ACCELERATION_3X_MS; 590 mRotaryService.onRotaryEvents(validDisplayId, 591 Collections.singletonList( 592 new RotaryEvent(inputType, true, new long[]{eventTime}))); 593 AccessibilityNodeInfo button4Node = createNode("button4"); 594 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button4Node); 595 596 // Move focus back to defaultFocus. 597 eventTime += ROTATION_ACCELERATION_3X_MS; 598 mRotaryService.onRotaryEvents(validDisplayId, 599 Collections.singletonList( 600 new RotaryEvent(inputType, false, new long[]{eventTime}))); 601 602 // Rotating the controller two detents clockwise somewhat fast should move the focus from 603 // defaultFocus to button5. 604 mRotaryService.onRotaryEvents(validDisplayId, Collections.singletonList( 605 new RotaryEvent(inputType, true, 606 new long[]{eventTime + ROTATION_ACCELERATION_2X_MS, 607 eventTime + ROTATION_ACCELERATION_2X_MS * 2}))); 608 AccessibilityNodeInfo button5Node = createNode("button5"); 609 assertThat(mRotaryService.getFocusedNode()).isEqualTo(button5Node); 610 } 611 612 /** 613 * Tests {@link RotaryService#nudgeTo(List, int)} in the following view tree: 614 * <pre> 615 * The HUN window: 616 * 617 * HUN FocusParkingView 618 * ==========HUN focus area========== 619 * = = 620 * = ............. ............. = 621 * = . . . . = 622 * = .hun button1. .hun button2. = 623 * = . . . . = 624 * = ............. ............. = 625 * = = 626 * ================================== 627 * 628 * The app window: 629 * 630 * app FocusParkingView 631 * ===========focus area 1=========== ===========focus area 2=========== 632 * = = = = 633 * = ............. ............. = = ............. ............. = 634 * = . . . . = = . . . . = 635 * = .app button1. . nudge . = = .app button2. . nudge . = 636 * = . . . shortcut1 . = = . . . shortcut2 . = 637 * = ............. ............. = = ............. ............. = 638 * = = = = 639 * ================================== ================================== 640 * 641 * ===========focus area 3=========== 642 * = = 643 * = ............. ............. = 644 * = . . . . = 645 * = .app button3. . default . = 646 * = . . . focus . = 647 * = ............. ............. = 648 * = = 649 * ================================== 650 * </pre> 651 */ 652 @Test testNudgeTo_nudgeToHun()653 public void testNudgeTo_nudgeToHun() { 654 initActivity(R.layout.rotary_service_test_2_activity); 655 656 AccessibilityNodeInfo hunRoot = createNode("hun_root"); 657 AccessibilityWindowInfo hunWindow = new WindowBuilder() 658 .setRoot(hunRoot) 659 .build(); 660 AccessibilityNodeInfo appRoot = createNode("app_root"); 661 AccessibilityWindowInfo appWindow = new WindowBuilder() 662 .setRoot(appRoot) 663 .build(); 664 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 665 windows.add(hunWindow); 666 windows.add(appWindow); 667 when(mRotaryService.getWindows()).thenReturn(windows); 668 669 AccessibilityNodeInfo hunButton1 = createNode("hun_button1"); 670 AccessibilityNodeInfo mockHunFpv = mock(AccessibilityNodeInfo.class); 671 doAnswer(invocation -> { 672 mRotaryService.setFocusedNode(hunButton1); 673 return true; 674 }).when(mockHunFpv).performAction(ACTION_RESTORE_DEFAULT_FOCUS); 675 when(mockHunFpv.refresh()).thenReturn(true); 676 when(mockHunFpv.getClassName()).thenReturn(Utils.FOCUS_PARKING_VIEW_CLASS_NAME); 677 when(mNavigator.findFocusParkingViewInRoot(hunRoot)).thenReturn(mockHunFpv); 678 when(mNavigator.findHunWindow(anyList())).thenReturn(hunWindow); 679 680 assertThat(mRotaryService.getFocusedNode()).isNotEqualTo(hunButton1); 681 682 int hunNudgeDirection = mRotaryService.mHunNudgeDirection; 683 mRotaryService.nudgeTo(windows, hunNudgeDirection); 684 assertThat(mRotaryService.getFocusedNode()).isEqualTo(hunButton1); 685 } 686 687 /** 688 * Tests {@link RotaryService#nudgeTo(List, int)} in the following view tree: 689 * <pre> 690 * The HUN window: 691 * 692 * HUN FocusParkingView 693 * ==========HUN focus area========== 694 * = = 695 * = ............. ............. = 696 * = . . . . = 697 * = .hun button1. .hun button2. = 698 * = . . . . = 699 * = ............. ............. = 700 * = = 701 * ================================== 702 * 703 * The app window: 704 * 705 * app FocusParkingView 706 * ===========focus area 1=========== ===========focus area 2=========== 707 * = = = = 708 * = ............. ............. = = ............. ............. = 709 * = . . . . = = . . . . = 710 * = .app button1. . nudge . = = .app button2. . nudge . = 711 * = . . . shortcut1 . = = . . . shortcut2 . = 712 * = ............. ............. = = ............. ............. = 713 * = = = = 714 * ================================== ================================== 715 * 716 * ===========focus area 3=========== 717 * = = 718 * = ............. ............. = 719 * = . . . . = 720 * = .app button3. . default . = 721 * = . . . focus . = 722 * = ............. ............. = 723 * = = 724 * ================================== 725 * </pre> 726 */ 727 @Test testNudgeTo_nudgeToNudgeShortcut_legacy()728 public void testNudgeTo_nudgeToNudgeShortcut_legacy() { 729 initActivity(R.layout.rotary_service_test_2_activity); 730 731 AccessibilityNodeInfo appRoot = createNode("app_root"); 732 AccessibilityWindowInfo appWindow = new WindowBuilder() 733 .setRoot(appRoot) 734 .build(); 735 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 736 windows.add(appWindow); 737 738 Activity activity = mActivityRule.getActivity(); 739 Button appButton1 = activity.findViewById(R.id.app_button1); 740 appButton1.post(() -> appButton1.requestFocus()); 741 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 742 assertThat(appButton1.isFocused()).isTrue(); 743 AccessibilityNodeInfo appButton1Node = createNode("app_button1"); 744 mRotaryService.setFocusedNode(appButton1Node); 745 746 mRotaryService.nudgeTo(windows, View.FOCUS_RIGHT); 747 AccessibilityNodeInfo nudgeShortcut1Node = createNode("nudge_shortcut1"); 748 assertThat(mRotaryService.getFocusedNode()).isEqualTo(nudgeShortcut1Node); 749 } 750 751 /** 752 * Tests {@link RotaryService#nudgeTo(List, int)} in the following view tree: 753 * <pre> 754 * The HUN window: 755 * 756 * HUN FocusParkingView 757 * ==========HUN focus area========== 758 * = = 759 * = ............. ............. = 760 * = . . . . = 761 * = .hun button1. .hun button2. = 762 * = . . . . = 763 * = ............. ............. = 764 * = = 765 * ================================== 766 * 767 * The app window: 768 * 769 * app FocusParkingView 770 * ===========focus area 1=========== ===========focus area 2=========== 771 * = = = = 772 * = ............. ............. = = ............. ............. = 773 * = . . . . = = . . . . = 774 * = .app button1. . nudge . = = .app button2. . nudge . = 775 * = . . . shortcut1 . = = . . . shortcut2 . = 776 * = ............. ............. = = ............. ............. = 777 * = = = = 778 * ================================== ================================== 779 * 780 * ===========focus area 3=========== 781 * = = 782 * = ............. ............. = 783 * = . . . . = 784 * = .app button3. . default . = 785 * = . . . focus . = 786 * = ............. ............. = 787 * = = 788 * ================================== 789 * </pre> 790 */ 791 @Test testNudgeTo_nudgeToNudgeShortcut_new()792 public void testNudgeTo_nudgeToNudgeShortcut_new() { 793 initActivity(R.layout.rotary_service_test_2_activity); 794 795 AccessibilityNodeInfo appRoot = createNode("app_root"); 796 AccessibilityWindowInfo appWindow = new WindowBuilder() 797 .setRoot(appRoot) 798 .build(); 799 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 800 windows.add(appWindow); 801 802 Activity activity = mActivityRule.getActivity(); 803 Button appButton2 = activity.findViewById(R.id.app_button2); 804 appButton2.post(() -> appButton2.requestFocus()); 805 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 806 assertThat(appButton2.isFocused()).isTrue(); 807 AccessibilityNodeInfo appButton2Node = createNode("app_button2"); 808 mRotaryService.setFocusedNode(appButton2Node); 809 810 mRotaryService.nudgeTo(windows, View.FOCUS_RIGHT); 811 AccessibilityNodeInfo nudgeShortcut2Node = createNode("nudge_shortcut2"); 812 assertThat(mRotaryService.getFocusedNode()).isEqualTo(nudgeShortcut2Node); 813 } 814 815 /** 816 * Tests {@link RotaryService#nudgeTo(List, int)} in the following view tree: 817 * <pre> 818 * The HUN window: 819 * 820 * HUN FocusParkingView 821 * ==========HUN focus area========== 822 * = = 823 * = ............. ............. = 824 * = . . . . = 825 * = .hun button1. .hun button2. = 826 * = . . . . = 827 * = ............. ............. = 828 * = = 829 * ================================== 830 * 831 * The app window: 832 * 833 * app FocusParkingView 834 * ===========focus area 1=========== ===========focus area 2=========== 835 * = = = = 836 * = ............. ............. = = ............. ............. = 837 * = . . . . = = . . . . = 838 * = .app button1. . nudge . = = .app button2. . nudge . = 839 * = . . . shortcut1 . = = . . . shortcut2 . = 840 * = ............. ............. = = ............. ............. = 841 * = = = = 842 * ================================== ================================== 843 * 844 * ===========focus area 3=========== 845 * = = 846 * = ............. ............. = 847 * = . . . . = 848 * = .app button3. . default . = 849 * = . . . focus . = 850 * = ............. ............. = 851 * = = 852 * ================================== 853 * </pre> 854 */ 855 @Test testNudgeTo_nudgeToUserSpecifiedTarget()856 public void testNudgeTo_nudgeToUserSpecifiedTarget() { 857 initActivity(R.layout.rotary_service_test_2_activity); 858 859 AccessibilityNodeInfo appRoot = createNode("app_root"); 860 AccessibilityWindowInfo appWindow = new WindowBuilder() 861 .setRoot(appRoot) 862 .build(); 863 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 864 windows.add(appWindow); 865 866 Activity activity = mActivityRule.getActivity(); 867 Button appButton2 = activity.findViewById(R.id.app_button2); 868 appButton2.post(() -> appButton2.requestFocus()); 869 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 870 assertThat(appButton2.isFocused()).isTrue(); 871 AccessibilityNodeInfo appButton2Node = createNode("app_button2"); 872 mRotaryService.setFocusedNode(appButton2Node); 873 874 mRotaryService.nudgeTo(windows, View.FOCUS_LEFT); 875 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 876 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 877 } 878 879 /** 880 * Tests {@link RotaryService#nudgeTo(List, int)} in the following view tree: 881 * <pre> 882 * The HUN window: 883 * 884 * HUN FocusParkingView 885 * ==========HUN focus area========== 886 * = = 887 * = ............. ............. = 888 * = . . . . = 889 * = .hun button1. .hun button2. = 890 * = . . . . = 891 * = ............. ............. = 892 * = = 893 * ================================== 894 * 895 * The app window: 896 * 897 * app FocusParkingView 898 * ===========focus area 1=========== ===========focus area 2=========== 899 * = = = = 900 * = ............. ............. = = ............. ............. = 901 * = . . . . = = . . . . = 902 * = .app button1. . nudge . = = .app button2. . nudge . = 903 * = . . . shortcut1 . = = . . . shortcut2 . = 904 * = ............. ............. = = ............. ............. = 905 * = = = = 906 * ================================== ================================== 907 * 908 * ===========focus area 3=========== 909 * = = 910 * = ............. ............. = 911 * = . . . . = 912 * = .app button3. . default . = 913 * = . . . focus . = 914 * = ............. ............. = 915 * = = 916 * ================================== 917 * </pre> 918 */ 919 @Test testNudgeTo_nudgeToNearestTarget()920 public void testNudgeTo_nudgeToNearestTarget() { 921 initActivity(R.layout.rotary_service_test_2_activity); 922 923 AccessibilityNodeInfo appRoot = createNode("app_root"); 924 AccessibilityWindowInfo appWindow = new WindowBuilder() 925 .setRoot(appRoot) 926 .build(); 927 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 928 windows.add(appWindow); 929 930 Activity activity = mActivityRule.getActivity(); 931 Button appButton3 = activity.findViewById(R.id.app_button3); 932 appButton3.post(() -> appButton3.requestFocus()); 933 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 934 assertThat(appButton3.isFocused()).isTrue(); 935 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 936 AccessibilityNodeInfo appFocusArea3Node = createNode("app_focus_area3"); 937 mRotaryService.setFocusedNode(appButton3Node); 938 939 AccessibilityNodeInfo appFocusArea1Node = createNode("app_focus_area1"); 940 when(mNavigator.findNudgeTargetFocusArea( 941 windows, appButton3Node, appFocusArea3Node, View.FOCUS_UP)) 942 .thenReturn(AccessibilityNodeInfo.obtain(appFocusArea1Node)); 943 944 mRotaryService.nudgeTo(windows, View.FOCUS_UP); 945 AccessibilityNodeInfo appButton1Node = createNode("app_button1"); 946 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appButton1Node); 947 } 948 949 /** 950 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 951 * <pre> 952 * The HUN window: 953 * 954 * hun FocusParkingView 955 * ==========HUN focus area========== 956 * = = 957 * = ............. ............. = 958 * = . . . . = 959 * = .hun button1. .hun button2. = 960 * = . . . . = 961 * = ............. ............. = 962 * = = 963 * ================================== 964 * 965 * The app window: 966 * 967 * app FocusParkingView 968 * ===========focus area 1=========== ===========focus area 2=========== 969 * = = = = 970 * = ............. ............. = = ............. ............. = 971 * = . . . . = = . . . . = 972 * = .app button1. . nudge . = = .app button2. . nudge . = 973 * = . . . shortcut1 . = = . . . shortcut2 . = 974 * = ............. ............. = = ............. ............. = 975 * = = = = 976 * ================================== ================================== 977 * 978 * ===========focus area 3=========== 979 * = = 980 * = ............. ............. = 981 * = . . . . = 982 * = .app button3. . default . = 983 * = . (source) . . focus . = 984 * = ............. ............. = 985 * = = 986 * ================================== 987 * </pre> 988 */ 989 @Test testOnKeyEvents_nudgeUp_moveFocus()990 public void testOnKeyEvents_nudgeUp_moveFocus() { 991 initActivity(R.layout.rotary_service_test_2_activity); 992 993 AccessibilityNodeInfo appRoot = createNode("app_root"); 994 AccessibilityWindowInfo appWindow = new WindowBuilder() 995 .setRoot(appRoot) 996 .build(); 997 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 998 windows.add(appWindow); 999 when(mRotaryService.getWindows()).thenReturn(windows); 1000 1001 Activity activity = mActivityRule.getActivity(); 1002 Button appButton3 = activity.findViewById(R.id.app_button3); 1003 appButton3.post(() -> appButton3.requestFocus()); 1004 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1005 assertThat(appButton3.isFocused()).isTrue(); 1006 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 1007 AccessibilityNodeInfo appFocusArea3Node = createNode("app_focus_area3"); 1008 mRotaryService.setFocusedNode(appButton3Node); 1009 1010 AccessibilityNodeInfo appFocusArea1Node = createNode("app_focus_area1"); 1011 when(mNavigator.findNudgeTargetFocusArea( 1012 windows, appButton3Node, appFocusArea3Node, View.FOCUS_UP)) 1013 .thenReturn(AccessibilityNodeInfo.obtain(appFocusArea1Node)); 1014 1015 // Nudge up the controller. 1016 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1017 KeyEvent nudgeUpEventActionDown = 1018 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP); 1019 mRotaryService.onKeyEvents(validDisplayId, 1020 Collections.singletonList(nudgeUpEventActionDown)); 1021 KeyEvent nudgeUpEventActionUp = 1022 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP); 1023 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeUpEventActionUp)); 1024 1025 // It should move focus to the FocusArea above. 1026 AccessibilityNodeInfo appButton1Node = createNode("app_button1"); 1027 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appButton1Node); 1028 } 1029 1030 /** 1031 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1032 * <pre> 1033 * The HUN window: 1034 * 1035 * hun FocusParkingView 1036 * ==========HUN focus area========== 1037 * = = 1038 * = ............. ............. = 1039 * = . . . . = 1040 * = .hun button1. .hun button2. = 1041 * = . . . . = 1042 * = ............. ............. = 1043 * = = 1044 * ================================== 1045 * 1046 * The app window: 1047 * 1048 * app FocusParkingView 1049 * ===========focus area 1=========== ===========focus area 2=========== 1050 * = = = = 1051 * = ............. ............. = = ............. ............. = 1052 * = . . . . = = . . . . = 1053 * = .app button1. . nudge . = = .app button2. . nudge . = 1054 * = . . . shortcut1 . = = . . . shortcut2 . = 1055 * = ............. ............. = = ............. ............. = 1056 * = = = = 1057 * ================================== ================================== 1058 * 1059 * ===========focus area 3=========== 1060 * = = 1061 * = ............. ............. = 1062 * = . . . . = 1063 * = .app button3. . default . = 1064 * = . . . focus . = 1065 * = ............. ............. = 1066 * = = 1067 * ================================== 1068 * </pre> 1069 */ 1070 @Test testOnKeyEvents_nudgeUp_initFocus()1071 public void testOnKeyEvents_nudgeUp_initFocus() { 1072 initActivity(R.layout.rotary_service_test_2_activity); 1073 1074 // RotaryService.mFocusedNode is not initialized. 1075 AccessibilityNodeInfo appRoot = createNode("app_root"); 1076 AccessibilityWindowInfo appWindow = new WindowBuilder() 1077 .setRoot(appRoot) 1078 .build(); 1079 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1080 windows.add(appWindow); 1081 when(mRotaryService.getWindows()).thenReturn(windows); 1082 when(mRotaryService.getRootInActiveWindow()) 1083 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1084 1085 // Move focus to the FocusParkingView. 1086 Activity activity = mActivityRule.getActivity(); 1087 FocusParkingView fpv = activity.findViewById(R.id.app_fpv); 1088 fpv.setShouldRestoreFocus(false); 1089 fpv.post(() -> fpv.requestFocus()); 1090 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1091 assertThat(fpv.isFocused()).isTrue(); 1092 assertNull(mRotaryService.getFocusedNode()); 1093 1094 // Nudge up the controller. 1095 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1096 KeyEvent nudgeUpEventActionDown = 1097 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP); 1098 mRotaryService.onKeyEvents(validDisplayId, 1099 Collections.singletonList(nudgeUpEventActionDown)); 1100 KeyEvent nudgeUpEventActionUp = 1101 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP); 1102 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeUpEventActionUp)); 1103 1104 // It should initialize the focus. 1105 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1106 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1107 } 1108 1109 /** 1110 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1111 * <pre> 1112 * The HUN window: 1113 * 1114 * hun FocusParkingView 1115 * ==========HUN focus area========== 1116 * = = 1117 * = ............. ............. = 1118 * = . . . . = 1119 * = .hun button1. .hun button2. = 1120 * = . (focused) . . . = 1121 * = ............. ............. = 1122 * = = 1123 * ================================== 1124 * 1125 * The app window: 1126 * 1127 * app FocusParkingView 1128 * ===========focus area 1=========== ===========focus area 2=========== 1129 * = = = = 1130 * = ............. ............. = = ............. ............. = 1131 * = . . . . = = . . . . = 1132 * = .app button1. . nudge . = = .app button2. . nudge . = 1133 * = . . . shortcut1 . = = . . . shortcut2 . = 1134 * = ............. ............. = = ............. ............. = 1135 * = = = = 1136 * ================================== ================================== 1137 * 1138 * ===========focus area 3=========== 1139 * = = 1140 * = ............. ............. = 1141 * = . . . . = 1142 * = .app button3. . default . = 1143 * = . . . focus . = 1144 * = ............. ............. = 1145 * = = 1146 * ================================== 1147 * </pre> 1148 */ 1149 @Test testOnKeyEvents_nudgeToHunEscapeNudgeDirection_leaveTheHun()1150 public void testOnKeyEvents_nudgeToHunEscapeNudgeDirection_leaveTheHun() { 1151 initActivity(R.layout.rotary_service_test_2_activity); 1152 1153 AccessibilityNodeInfo appRoot = createNode("app_root"); 1154 AccessibilityWindowInfo appWindow = new WindowBuilder() 1155 .setRoot(appRoot) 1156 .build(); 1157 AccessibilityNodeInfo hunRoot = createNode("hun_root"); 1158 AccessibilityWindowInfo hunWindow = new WindowBuilder() 1159 .setRoot(hunRoot) 1160 .build(); 1161 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1162 windows.add(appWindow); 1163 windows.add(hunWindow); 1164 when(mRotaryService.getWindows()).thenReturn(windows); 1165 1166 // A Button in the HUN window is focused. 1167 Activity activity = mActivityRule.getActivity(); 1168 Button hunButton1 = activity.findViewById(R.id.hun_button1); 1169 hunButton1.post(() -> hunButton1.requestFocus()); 1170 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1171 assertThat(hunButton1.isFocused()).isTrue(); 1172 AccessibilityNodeInfo hunButton1Node = createNode("hun_button1"); 1173 AccessibilityNodeInfo hunFocusAreaNode = createNode("hun_focus_area"); 1174 mRotaryService.setFocusedNode(hunButton1Node); 1175 1176 // Set HUN escape nudge direction to View.FOCUS_DOWN. 1177 mRotaryService.mHunEscapeNudgeDirection = View.FOCUS_DOWN; 1178 1179 AccessibilityNodeInfo appFocusArea3Node = createNode("app_focus_area3"); 1180 when(mNavigator.findNudgeTargetFocusArea( 1181 windows, hunButton1Node, hunFocusAreaNode, View.FOCUS_DOWN)) 1182 .thenReturn(AccessibilityNodeInfo.obtain(appFocusArea3Node)); 1183 1184 // Nudge down the controller. 1185 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1186 KeyEvent nudgeEventActionDown = 1187 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN); 1188 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeEventActionDown)); 1189 KeyEvent nudgeEventActionUp = 1190 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN); 1191 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeEventActionUp)); 1192 1193 // Nudging down should exit the HUN and focus in app_focus_area3. 1194 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1195 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1196 } 1197 1198 /** 1199 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1200 * <pre> 1201 * The HUN window: 1202 * 1203 * hun FocusParkingView 1204 * ==========HUN focus area========== 1205 * = = 1206 * = ............. ............. = 1207 * = . . . . = 1208 * = .hun button1. .hun button2. = 1209 * = . (focused) . . . = 1210 * = ............. ............. = 1211 * = = 1212 * ================================== 1213 * 1214 * The app window: 1215 * 1216 * app FocusParkingView 1217 * ===========focus area 1=========== ===========focus area 2=========== 1218 * = = = = 1219 * = ............. ............. = = ............. ............. = 1220 * = . . . . = = . . . . = 1221 * = .app button1. . nudge . = = .app button2. . nudge . = 1222 * = . . . shortcut1 . = = . . . shortcut2 . = 1223 * = ............. ............. = = ............. ............. = 1224 * = = = = 1225 * ================================== ================================== 1226 * 1227 * ===========focus area 3=========== 1228 * = = 1229 * = ............. ............. = 1230 * = . . . . = 1231 * = .app button3. . default . = 1232 * = . . . focus . = 1233 * = ............. ............. = 1234 * = = 1235 * ================================== 1236 * </pre> 1237 */ 1238 @Test testOnKeyEvents_nudgeToNonHunEscapeNudgeDirection_stayInTheHun()1239 public void testOnKeyEvents_nudgeToNonHunEscapeNudgeDirection_stayInTheHun() { 1240 initActivity(R.layout.rotary_service_test_2_activity); 1241 1242 AccessibilityNodeInfo appRoot = createNode("app_root"); 1243 AccessibilityWindowInfo appWindow = new WindowBuilder() 1244 .setRoot(appRoot) 1245 .build(); 1246 AccessibilityNodeInfo hunRoot = createNode("hun_root"); 1247 AccessibilityWindowInfo hunWindow = new WindowBuilder() 1248 .setRoot(hunRoot) 1249 .build(); 1250 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1251 windows.add(appWindow); 1252 windows.add(hunWindow); 1253 when(mRotaryService.getWindows()).thenReturn(windows); 1254 1255 // A Button in the HUN window is focused. 1256 Activity activity = mActivityRule.getActivity(); 1257 Button hunButton1 = activity.findViewById(R.id.hun_button1); 1258 hunButton1.post(() -> hunButton1.requestFocus()); 1259 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1260 assertThat(hunButton1.isFocused()).isTrue(); 1261 AccessibilityNodeInfo hunButton1Node = createNode("hun_button1"); 1262 AccessibilityNodeInfo hunFocusAreaNode = createNode("hun_focus_area"); 1263 mRotaryService.setFocusedNode(hunButton1Node); 1264 1265 // Set HUN escape nudge direction to View.FOCUS_UP. 1266 mRotaryService.mHunEscapeNudgeDirection = View.FOCUS_UP; 1267 1268 when(mNavigator.isHunWindow(hunButton1Node.getWindow())).thenReturn(true); 1269 1270 AccessibilityNodeInfo appFocusArea3Node = createNode("app_focus_area3"); 1271 when(mNavigator.findNudgeTargetFocusArea( 1272 windows, hunButton1Node, hunFocusAreaNode, View.FOCUS_DOWN)) 1273 .thenReturn(AccessibilityNodeInfo.obtain(appFocusArea3Node)); 1274 1275 // Nudge down the controller. 1276 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1277 KeyEvent nudgeEventActionDown = 1278 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN); 1279 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeEventActionDown)); 1280 KeyEvent nudgeEventActionUp = 1281 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN); 1282 mRotaryService.onKeyEvents(validDisplayId, Collections.singletonList(nudgeEventActionUp)); 1283 1284 // Nudging down should stay in the HUN because HUN escape nudge direction is View.FOCUS_UP. 1285 assertThat(mRotaryService.getFocusedNode()).isEqualTo(hunButton1Node); 1286 } 1287 1288 /** 1289 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1290 * <pre> 1291 * The HUN window: 1292 * 1293 * hun FocusParkingView 1294 * ==========HUN focus area========== 1295 * = = 1296 * = ............. ............. = 1297 * = . . . . = 1298 * = .hun button1. .hun button2. = 1299 * = . . . . = 1300 * = ............. ............. = 1301 * = = 1302 * ================================== 1303 * 1304 * The app window: 1305 * 1306 * app FocusParkingView 1307 * ===========focus area 1=========== ===========focus area 2=========== 1308 * = = = = 1309 * = ............. ............. = = ............. ............. = 1310 * = . . . . = = . . . . = 1311 * = .app button1. . nudge . = = .app button2. . nudge . = 1312 * = . . . shortcut1 . = = . . . shortcut2 . = 1313 * = ............. ............. = = ............. ............. = 1314 * = = = = 1315 * ================================== ================================== 1316 * 1317 * ===========focus area 3=========== 1318 * = = 1319 * = ............. ............. = 1320 * = . . . . = 1321 * = .app button3. . default . = 1322 * = . . . focus . = 1323 * = ............. ............. = 1324 * = = 1325 * ================================== 1326 * </pre> 1327 */ 1328 @Test testOnKeyEvents_centerButtonClick_initFocus()1329 public void testOnKeyEvents_centerButtonClick_initFocus() { 1330 initActivity(R.layout.rotary_service_test_2_activity); 1331 1332 // RotaryService.mFocusedNode is not initialized. 1333 AccessibilityNodeInfo appRoot = createNode("app_root"); 1334 AccessibilityWindowInfo appWindow = new WindowBuilder() 1335 .setRoot(appRoot) 1336 .build(); 1337 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1338 windows.add(appWindow); 1339 when(mRotaryService.getWindows()).thenReturn(windows); 1340 when(mRotaryService.getRootInActiveWindow()) 1341 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1342 assertThat(mRotaryService.getFocusedNode()).isNull(); 1343 1344 // Click the center button of the controller. 1345 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1346 KeyEvent centerButtonEventActionDown = 1347 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1348 mRotaryService.onKeyEvents(validDisplayId, 1349 Collections.singletonList(centerButtonEventActionDown)); 1350 KeyEvent centerButtonEventActionUp = 1351 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1352 mRotaryService.onKeyEvents(validDisplayId, 1353 Collections.singletonList(centerButtonEventActionUp)); 1354 1355 // It should initialize the focus. 1356 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1357 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1358 } 1359 1360 /** 1361 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1362 * <pre> 1363 * The HUN window: 1364 * 1365 * hun FocusParkingView 1366 * ==========HUN focus area========== 1367 * = = 1368 * = ............. ............. = 1369 * = . . . . = 1370 * = .hun button1. .hun button2. = 1371 * = . . . . = 1372 * = ............. ............. = 1373 * = = 1374 * ================================== 1375 * 1376 * The app window: 1377 * 1378 * app FocusParkingView 1379 * ===========focus area 1=========== ===========focus area 2=========== 1380 * = = = = 1381 * = ............. ............. = = ............. ............. = 1382 * = . . . . = = . . . . = 1383 * = .app button1. . nudge . = = .app button2. . nudge . = 1384 * = . . . shortcut1 . = = . . . shortcut2 . = 1385 * = ............. ............. = = ............. ............. = 1386 * = = = = 1387 * ================================== ================================== 1388 * 1389 * ===========focus area 3=========== 1390 * = = 1391 * = ............. ............. = 1392 * = . . . . = 1393 * = .app button3. . default . = 1394 * = . (focused) . . focus . = 1395 * = ............. ............. = 1396 * = = 1397 * ================================== 1398 * </pre> 1399 */ 1400 @Test testOnKeyEvents_centerButtonClickInAppWindow_injectDpadCenterEvent()1401 public void testOnKeyEvents_centerButtonClickInAppWindow_injectDpadCenterEvent() { 1402 initActivity(R.layout.rotary_service_test_2_activity); 1403 1404 AccessibilityNodeInfo appRoot = createNode("app_root"); 1405 AccessibilityWindowInfo appWindow = new WindowBuilder() 1406 .setRoot(appRoot) 1407 .setType(TYPE_APPLICATION) 1408 .setFocused(true) 1409 .build(); 1410 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1411 windows.add(appWindow); 1412 when(mRotaryService.getWindows()).thenReturn(windows); 1413 when(mRotaryService.getRootInActiveWindow()) 1414 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1415 1416 AccessibilityNodeInfo mockAppButton3Node = mNodeBuilder 1417 .setFocused(true) 1418 .setWindow(appWindow) 1419 .build(); 1420 mRotaryService.setFocusedNode(mockAppButton3Node); 1421 1422 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1423 1424 // Click the center button of the controller. 1425 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1426 KeyEvent centerButtonEventActionDown = 1427 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1428 mRotaryService.onKeyEvents(validDisplayId, 1429 Collections.singletonList(centerButtonEventActionDown)); 1430 KeyEvent centerButtonEventActionUp = 1431 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1432 mRotaryService.onKeyEvents(validDisplayId, 1433 Collections.singletonList(centerButtonEventActionUp)); 1434 1435 // RotaryService should inject KEYCODE_DPAD_CENTER event because mockAppButton3Node is in 1436 // the application window. 1437 verify(mRotaryService, times(1)) 1438 .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_DOWN); 1439 verify(mRotaryService, times(1)) 1440 .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_UP); 1441 assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(mockAppButton3Node); 1442 assertThat(mRotaryService.getFocusedNode()).isEqualTo(mockAppButton3Node); 1443 } 1444 1445 /** 1446 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1447 * <pre> 1448 * The HUN window: 1449 * 1450 * hun FocusParkingView 1451 * ==========HUN focus area========== 1452 * = = 1453 * = ............. ............. = 1454 * = . . . . = 1455 * = .hun button1. .hun button2. = 1456 * = . . . . = 1457 * = ............. ............. = 1458 * = = 1459 * ================================== 1460 * 1461 * The app window: 1462 * 1463 * app FocusParkingView 1464 * ===========focus area 1=========== ===========focus area 2=========== 1465 * = = = = 1466 * = ............. ............. = = ............. ............. = 1467 * = . . . . = = . . . . = 1468 * = .app button1. . nudge . = = .app button2. . nudge . = 1469 * = . . . shortcut1 . = = . . . shortcut2 . = 1470 * = ............. ............. = = ............. ............. = 1471 * = = = = 1472 * ================================== ================================== 1473 * 1474 * ==============focus area 3============== 1475 * = = 1476 * = ................... = 1477 * = . WebView . ............. = 1478 * = . ............. . . . = 1479 * = . .app button3. . . default . = 1480 * = . . (focused) . . . focus . = 1481 * = . ............. . ............. = 1482 * = ................... = 1483 * = = 1484 * ======================================== 1485 * </pre> 1486 */ 1487 @Test testOnKeyEvents_centerButtonClickInAppWindow_webViewFocused_injectEnterKeyEvent()1488 public void testOnKeyEvents_centerButtonClickInAppWindow_webViewFocused_injectEnterKeyEvent() { 1489 initActivity(R.layout.rotary_service_test_2_activity); 1490 1491 AccessibilityNodeInfo appRoot = createNode("app_root"); 1492 AccessibilityWindowInfo appWindow = new WindowBuilder() 1493 .setRoot(appRoot) 1494 .setType(TYPE_APPLICATION) 1495 .setFocused(true) 1496 .build(); 1497 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1498 windows.add(appWindow); 1499 when(mRotaryService.getWindows()).thenReturn(windows); 1500 when(mRotaryService.getRootInActiveWindow()) 1501 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1502 1503 AccessibilityNodeInfo mockWebViewParent = mNodeBuilder 1504 .setClassName(Utils.WEB_VIEW_CLASS_NAME) 1505 .setWindow(appWindow) 1506 .build(); 1507 1508 AccessibilityNodeInfo mockAppButton3Node = mNodeBuilder 1509 .setFocused(true) 1510 .setParent(mockWebViewParent) 1511 .setWindow(appWindow) 1512 .build(); 1513 mRotaryService.setFocusedNode(mockAppButton3Node); 1514 1515 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1516 1517 // Click the center button of the controller. 1518 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1519 KeyEvent centerButtonEventActionDown = 1520 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1521 mRotaryService.onKeyEvents(validDisplayId, 1522 Collections.singletonList(centerButtonEventActionDown)); 1523 KeyEvent centerButtonEventActionUp = 1524 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1525 mRotaryService.onKeyEvents(validDisplayId, 1526 Collections.singletonList(centerButtonEventActionUp)); 1527 1528 // RotaryService should inject KEYCODE_ENTER event because mockAppButton3Node is in 1529 // the application window, its parent is a WebView, and it is not checkable. 1530 verify(mRotaryService, times(1)) 1531 .injectKeyEvent(KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_DOWN); 1532 verify(mRotaryService, times(1)) 1533 .injectKeyEvent(KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_UP); 1534 assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(mockAppButton3Node); 1535 assertThat(mRotaryService.getFocusedNode()).isEqualTo(mockAppButton3Node); 1536 } 1537 1538 /** 1539 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1540 * <pre> 1541 * The HUN window: 1542 * 1543 * hun FocusParkingView 1544 * ==========HUN focus area========== 1545 * = = 1546 * = ............. ............. = 1547 * = . . . . = 1548 * = .hun button1. .hun button2. = 1549 * = . . . . = 1550 * = ............. ............. = 1551 * = = 1552 * ================================== 1553 * 1554 * The app window: 1555 * 1556 * app FocusParkingView 1557 * ===========focus area 1=========== ===========focus area 2=========== 1558 * = = = = 1559 * = ............. ............. = = ............. ............. = 1560 * = . . . . = = . . . . = 1561 * = .app button1. . nudge . = = .app button2. . nudge . = 1562 * = . . . shortcut1 . = = . . . shortcut2 . = 1563 * = ............. ............. = = ............. ............. = 1564 * = = = = 1565 * ================================== ================================== 1566 * 1567 * ==============focus area 3============== 1568 * = = 1569 * = ................... = 1570 * = . WebView . ............. = 1571 * = . ............. . . . = 1572 * = . .app button3. . . default . = 1573 * = . . (focused) . . . focus . = 1574 * = . ............. . ............. = 1575 * = ................... = 1576 * = = 1577 * ======================================== 1578 * </pre> 1579 */ 1580 @Test 1581 public void testOnKeyEvents_centerButtonClickInAppWindow_webViewFocused_isCheckable_injectSpaceKeyEvent()1582 testOnKeyEvents_centerButtonClickInAppWindow_webViewFocused_isCheckable_injectSpaceKeyEvent() { 1583 initActivity(R.layout.rotary_service_test_2_activity); 1584 1585 AccessibilityNodeInfo appRoot = createNode("app_root"); 1586 AccessibilityWindowInfo appWindow = new WindowBuilder() 1587 .setRoot(appRoot) 1588 .setType(TYPE_APPLICATION) 1589 .setFocused(true) 1590 .build(); 1591 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1592 windows.add(appWindow); 1593 when(mRotaryService.getWindows()).thenReturn(windows); 1594 when(mRotaryService.getRootInActiveWindow()) 1595 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1596 1597 AccessibilityNodeInfo mockWebViewParent = mNodeBuilder 1598 .setClassName(Utils.WEB_VIEW_CLASS_NAME) 1599 .setWindow(appWindow) 1600 .build(); 1601 1602 AccessibilityNodeInfo mockAppButton3Node = mNodeBuilder 1603 .setFocused(true) 1604 .setCheckable(true) 1605 .setParent(mockWebViewParent) 1606 .setWindow(appWindow) 1607 .build(); 1608 mRotaryService.setFocusedNode(mockAppButton3Node); 1609 1610 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1611 1612 // Click the center button of the controller. 1613 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1614 KeyEvent centerButtonEventActionDown = 1615 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1616 mRotaryService.onKeyEvents(validDisplayId, 1617 Collections.singletonList(centerButtonEventActionDown)); 1618 KeyEvent centerButtonEventActionUp = 1619 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1620 mRotaryService.onKeyEvents(validDisplayId, 1621 Collections.singletonList(centerButtonEventActionUp)); 1622 1623 // RotaryService should inject KEYCODE_SPACE event because mockAppButton3Node is in 1624 // the application window, its parent is a WebView, and it is checkable. 1625 verify(mRotaryService, times(1)) 1626 .injectKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_DOWN); 1627 verify(mRotaryService, times(1)) 1628 .injectKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_UP); 1629 assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(mockAppButton3Node); 1630 assertThat(mRotaryService.getFocusedNode()).isEqualTo(mockAppButton3Node); 1631 } 1632 1633 /** 1634 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1635 * <pre> 1636 * The HUN window: 1637 * 1638 * hun FocusParkingView 1639 * ==========HUN focus area========== 1640 * = = 1641 * = ............. ............. = 1642 * = . . . . = 1643 * = .hun button1. .hun button2. = 1644 * = . . . . = 1645 * = ............. ............. = 1646 * = = 1647 * ================================== 1648 * 1649 * The app window: 1650 * 1651 * app FocusParkingView 1652 * ===========focus area 1=========== ===========focus area 2=========== 1653 * = = = = 1654 * = ............. ............. = = ............. ............. = 1655 * = . . . . = = . . . . = 1656 * = .app button1. . nudge . = = .app button2. . nudge . = 1657 * = . . . shortcut1 . = = . . . shortcut2 . = 1658 * = ............. ............. = = ............. ............. = 1659 * = = = = 1660 * ================================== ================================== 1661 * 1662 * ===========focus area 3=========== 1663 * = = 1664 * = ............. ............. = 1665 * = . . . . = 1666 * = .app button3. . default . = 1667 * = . (focused) . . focus . = 1668 * = ............. ............. = 1669 * = = 1670 * ================================== 1671 * </pre> 1672 */ 1673 @Test testOnKeyEvents_centerButtonClickInSystemWindow_performActionClick()1674 public void testOnKeyEvents_centerButtonClickInSystemWindow_performActionClick() { 1675 initActivity(R.layout.rotary_service_test_2_activity); 1676 1677 AccessibilityNodeInfo appRoot = createNode("app_root"); 1678 AccessibilityWindowInfo appWindow = new WindowBuilder() 1679 .setRoot(appRoot) 1680 .build(); 1681 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1682 windows.add(appWindow); 1683 when(mRotaryService.getWindows()).thenReturn(windows); 1684 when(mRotaryService.getRootInActiveWindow()) 1685 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1686 1687 Activity activity = mActivityRule.getActivity(); 1688 Button appButton3 = activity.findViewById(R.id.app_button3); 1689 appButton3.setOnClickListener(v -> v.setActivated(true)); 1690 appButton3.post(() -> appButton3.requestFocus()); 1691 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1692 assertThat(appButton3.isFocused()).isTrue(); 1693 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 1694 mRotaryService.setFocusedNode(appButton3Node); 1695 mRotaryService.mLongPressMs = 400; 1696 1697 assertThat(appButton3.isActivated()).isFalse(); 1698 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1699 1700 // Pretend that appButton3Node is in a window without focus. So RotaryService 1701 // should perform ACTION_CLICK on it when rotary center button is clicked. 1702 when(mRotaryService.isInFocusedWindow(appButton3Node)).thenReturn(false); 1703 // Click the center button of the controller. 1704 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1705 KeyEvent centerButtonEventActionDown = 1706 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1707 mRotaryService.onKeyEvents(validDisplayId, 1708 Collections.singletonList(centerButtonEventActionDown)); 1709 KeyEvent centerButtonEventActionUp = 1710 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1711 mRotaryService.onKeyEvents(validDisplayId, 1712 Collections.singletonList(centerButtonEventActionUp)); 1713 1714 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1715 assertThat(appButton3.isActivated()).isTrue(); 1716 assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(appButton3Node); 1717 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appButton3Node); 1718 } 1719 1720 /** 1721 * Tests {@link RotaryService#onKeyEvents} in the following view tree: 1722 * <pre> 1723 * The HUN window: 1724 * 1725 * hun FocusParkingView 1726 * ==========HUN focus area========== 1727 * = = 1728 * = ............. ............. = 1729 * = . . . . = 1730 * = .hun button1. .hun button2. = 1731 * = . . . . = 1732 * = ............. ............. = 1733 * = = 1734 * ================================== 1735 * 1736 * The app window: 1737 * 1738 * app FocusParkingView 1739 * ===========focus area 1=========== ===========focus area 2=========== 1740 * = = = = 1741 * = ............. ............. = = ............. ............. = 1742 * = . . . . = = . . . . = 1743 * = .app button1. . nudge . = = .app button2. . nudge . = 1744 * = . . . shortcut1 . = = . . . shortcut2 . = 1745 * = ............. ............. = = ............. ............. = 1746 * = = = = 1747 * ================================== ================================== 1748 * 1749 * ===========focus area 3=========== 1750 * = = 1751 * = ............. ............. = 1752 * = . . . . = 1753 * = .app button3. . default . = 1754 * = . (focused) . . focus . = 1755 * = ............. ............. = 1756 * = = 1757 * ================================== 1758 * </pre> 1759 */ 1760 @Test testOnKeyEvents_centerButtonLongClickInSystemWindow_performActionLongClick()1761 public void testOnKeyEvents_centerButtonLongClickInSystemWindow_performActionLongClick() { 1762 initActivity(R.layout.rotary_service_test_2_activity); 1763 1764 AccessibilityNodeInfo appRoot = createNode("app_root"); 1765 AccessibilityWindowInfo appWindow = new WindowBuilder() 1766 .setRoot(appRoot) 1767 .build(); 1768 List<AccessibilityWindowInfo> windows = new ArrayList<>(); 1769 windows.add(appWindow); 1770 when(mRotaryService.getWindows()).thenReturn(windows); 1771 when(mRotaryService.getRootInActiveWindow()) 1772 .thenReturn(MockNodeCopierProvider.get().copy(mWindowRoot)); 1773 1774 Activity activity = mActivityRule.getActivity(); 1775 Button appButton3 = activity.findViewById(R.id.app_button3); 1776 appButton3.setOnLongClickListener(v -> { 1777 v.setActivated(true); 1778 return true; 1779 }); 1780 appButton3.post(() -> appButton3.requestFocus()); 1781 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1782 assertThat(appButton3.isFocused()).isTrue(); 1783 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 1784 mRotaryService.setFocusedNode(appButton3Node); 1785 mRotaryService.mLongPressMs = 0; 1786 1787 assertThat(appButton3.isActivated()).isFalse(); 1788 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1789 1790 // Pretend that appButton3Node is in a window without focus. So RotaryService 1791 // should perform ACTION_CLICK on it when rotary center button is clicked. 1792 when(mRotaryService.isInFocusedWindow(appButton3Node)).thenReturn(false); 1793 // Click the center button of the controller. 1794 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 1795 KeyEvent centerButtonEventActionDown = 1796 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 1797 mRotaryService.onKeyEvents(validDisplayId, 1798 Collections.singletonList(centerButtonEventActionDown)); 1799 KeyEvent centerButtonEventActionUp = 1800 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 1801 mRotaryService.onKeyEvents(validDisplayId, 1802 Collections.singletonList(centerButtonEventActionUp)); 1803 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 1804 1805 assertThat(appButton3.isActivated()).isTrue(); 1806 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 1807 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appButton3Node); 1808 } 1809 1810 /** 1811 * Tests {@link RotaryService#onAccessibilityEvent} in the following view tree: 1812 * <pre> 1813 * The HUN window: 1814 * 1815 * hun FocusParkingView 1816 * ==========HUN focus area========== 1817 * = = 1818 * = ............. ............. = 1819 * = . . . . = 1820 * = .hun button1. .hun button2. = 1821 * = . (focused) . . . = 1822 * = ............. ............. = 1823 * = = 1824 * ================================== 1825 * 1826 * The app window: 1827 * 1828 * app FocusParkingView 1829 * ===========focus area 1=========== ===========focus area 2=========== 1830 * = = = = 1831 * = ............. ............. = = ............. ............. = 1832 * = . . . . = = . . . . = 1833 * = .app button1. . nudge . = = .app button2. . nudge . = 1834 * = . . . shortcut1 . = = . . . shortcut2 . = 1835 * = ............. ............. = = ............. ............. = 1836 * = = = = 1837 * ================================== ================================== 1838 * 1839 * ===========focus area 3=========== 1840 * = = 1841 * = ............. ............. = 1842 * = . . . . = 1843 * = .app button3. . default . = 1844 * = . . . focus . = 1845 * = ............. ............. = 1846 * = = 1847 * ================================== 1848 * </pre> 1849 */ 1850 @Test testOnAccessibilityEvent_typeViewFocused()1851 public void testOnAccessibilityEvent_typeViewFocused() { 1852 initActivity(R.layout.rotary_service_test_2_activity); 1853 1854 // The app focuses appDefaultFocus, then the accessibility framework sends a 1855 // TYPE_VIEW_FOCUSED event. 1856 // RotaryService should set mFocusedNode to appDefaultFocusNode. 1857 1858 Activity activity = mActivityRule.getActivity(); 1859 Button appDefaultFocus = activity.findViewById(R.id.app_default_focus); 1860 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1861 assertThat(appDefaultFocus.isFocused()).isTrue(); 1862 assertThat(mRotaryService.getFocusedNode()).isNull(); 1863 1864 mRotaryService.mInRotaryMode = true; 1865 AccessibilityEvent event = mock(AccessibilityEvent.class); 1866 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appDefaultFocusNode)); 1867 when(event.getEventType()).thenReturn(TYPE_VIEW_FOCUSED); 1868 mRotaryService.onAccessibilityEvent(event); 1869 1870 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1871 } 1872 1873 /** 1874 * Tests {@link RotaryService#onAccessibilityEvent} in the following view tree: 1875 * <pre> 1876 * The HUN window: 1877 * 1878 * hun FocusParkingView 1879 * ==========HUN focus area========== 1880 * = = 1881 * = ............. ............. = 1882 * = . . . . = 1883 * = .hun button1. .hun button2. = 1884 * = . (focused) . . . = 1885 * = ............. ............. = 1886 * = = 1887 * ================================== 1888 * 1889 * The app window: 1890 * 1891 * app FocusParkingView 1892 * ===========focus area 1=========== ===========focus area 2=========== 1893 * = = = = 1894 * = ............. ............. = = ............. ............. = 1895 * = . . . . = = . . . . = 1896 * = .app button1. . nudge . = = .app button2. . nudge . = 1897 * = . . . shortcut1 . = = . . . shortcut2 . = 1898 * = ............. ............. = = ............. ............. = 1899 * = = = = 1900 * ================================== ================================== 1901 * 1902 * ===========focus area 3=========== 1903 * = = 1904 * = ............. ............. = 1905 * = . . . . = 1906 * = .app button3. . default . = 1907 * = . . . focus . = 1908 * = ............. ............. = 1909 * = = 1910 * ================================== 1911 * </pre> 1912 */ 1913 @Test testOnAccessibilityEvent_typeViewFocused2()1914 public void testOnAccessibilityEvent_typeViewFocused2() { 1915 initActivity(R.layout.rotary_service_test_2_activity); 1916 1917 // RotaryService focuses appDefaultFocus, then the app focuses on the FocusParkingView 1918 // and the accessibility framework sends a TYPE_VIEW_FOCUSED event. 1919 // RotaryService should set mFocusedNode to null. 1920 1921 Activity activity = mActivityRule.getActivity(); 1922 Button appDefaultFocus = activity.findViewById(R.id.app_default_focus); 1923 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1924 assertThat(appDefaultFocus.isFocused()).isTrue(); 1925 mRotaryService.setFocusedNode(appDefaultFocusNode); 1926 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1927 1928 mRotaryService.mInRotaryMode = true; 1929 1930 AccessibilityNodeInfo fpvNode = createNode("app_fpv"); 1931 AccessibilityEvent event = mock(AccessibilityEvent.class); 1932 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(fpvNode)); 1933 when(event.getEventType()).thenReturn(TYPE_VIEW_FOCUSED); 1934 mRotaryService.onAccessibilityEvent(event); 1935 1936 assertThat(mRotaryService.getFocusedNode()).isNull(); 1937 } 1938 1939 /** 1940 * Tests {@link RotaryService#onAccessibilityEvent} in the following view tree: 1941 * <pre> 1942 * The HUN window: 1943 * 1944 * hun FocusParkingView 1945 * ==========HUN focus area========== 1946 * = = 1947 * = ............. ............. = 1948 * = . . . . = 1949 * = .hun button1. .hun button2. = 1950 * = . (focused) . . . = 1951 * = ............. ............. = 1952 * = = 1953 * ================================== 1954 * 1955 * The app window: 1956 * 1957 * app FocusParkingView 1958 * ===========focus area 1=========== ===========focus area 2=========== 1959 * = = = = 1960 * = ............. ............. = = ............. ............. = 1961 * = . . . . = = . . . . = 1962 * = .app button1. . nudge . = = .app button2. . nudge . = 1963 * = . . . shortcut1 . = = . . . shortcut2 . = 1964 * = ............. ............. = = ............. ............. = 1965 * = = = = 1966 * ================================== ================================== 1967 * 1968 * ===========focus area 3=========== 1969 * = = 1970 * = ............. ............. = 1971 * = . . . . = 1972 * = .app button3. . default . = 1973 * = . . . focus . = 1974 * = ............. ............. = 1975 * = = 1976 * ================================== 1977 * </pre> 1978 */ 1979 @Test testOnAccessibilityEvent_typeViewClicked()1980 public void testOnAccessibilityEvent_typeViewClicked() { 1981 initActivity(R.layout.rotary_service_test_2_activity); 1982 1983 // The focus is on appDefaultFocus, then the user clicks it via the rotary controller. 1984 1985 Activity activity = mActivityRule.getActivity(); 1986 Button appDefaultFocus = activity.findViewById(R.id.app_default_focus); 1987 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 1988 assertThat(appDefaultFocus.isFocused()).isTrue(); 1989 mRotaryService.setFocusedNode(appDefaultFocusNode); 1990 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 1991 1992 mRotaryService.mInRotaryMode = true; 1993 mRotaryService.mIgnoreViewClickedNode = AccessibilityNodeInfo.obtain(appDefaultFocusNode); 1994 1995 AccessibilityEvent event = mock(AccessibilityEvent.class); 1996 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appDefaultFocusNode)); 1997 when(event.getEventType()).thenReturn(TYPE_VIEW_CLICKED); 1998 when(event.getEventTime()).thenReturn(-1l); 1999 mRotaryService.onAccessibilityEvent(event); 2000 2001 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 2002 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 2003 assertThat(mRotaryService.mLastTouchedNode).isNull(); 2004 } 2005 2006 /** 2007 * Tests {@link RotaryService#onAccessibilityEvent} in the following view tree: 2008 * <pre> 2009 * The HUN window: 2010 * 2011 * hun FocusParkingView 2012 * ==========HUN focus area========== 2013 * = = 2014 * = ............. ............. = 2015 * = . . . . = 2016 * = .hun button1. .hun button2. = 2017 * = . (focused) . . . = 2018 * = ............. ............. = 2019 * = = 2020 * ================================== 2021 * 2022 * The app window: 2023 * 2024 * app FocusParkingView 2025 * ===========focus area 1=========== ===========focus area 2=========== 2026 * = = = = 2027 * = ............. ............. = = ............. ............. = 2028 * = . . . . = = . . . . = 2029 * = .app button1. . nudge . = = .app button2. . nudge . = 2030 * = . . . shortcut1 . = = . . . shortcut2 . = 2031 * = ............. ............. = = ............. ............. = 2032 * = = = = 2033 * ================================== ================================== 2034 * 2035 * ===========focus area 3=========== 2036 * = = 2037 * = ............. ............. = 2038 * = . . . . = 2039 * = .app button3. . default . = 2040 * = . . . focus . = 2041 * = ............. ............. = 2042 * = = 2043 * ================================== 2044 * </pre> 2045 */ 2046 @Test testOnAccessibilityEvent_typeViewClicked2()2047 public void testOnAccessibilityEvent_typeViewClicked2() { 2048 initActivity(R.layout.rotary_service_test_2_activity); 2049 2050 // The focus is on appDefaultFocus, then the user clicks appButton3 via the touch screen. 2051 2052 Activity activity = mActivityRule.getActivity(); 2053 Button appDefaultFocus = activity.findViewById(R.id.app_default_focus); 2054 AccessibilityNodeInfo appDefaultFocusNode = createNode("app_default_focus"); 2055 assertThat(appDefaultFocus.isFocused()).isTrue(); 2056 mRotaryService.setFocusedNode(appDefaultFocusNode); 2057 assertThat(mRotaryService.getFocusedNode()).isEqualTo(appDefaultFocusNode); 2058 2059 mRotaryService.mInRotaryMode = true; 2060 2061 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 2062 AccessibilityEvent event = mock(AccessibilityEvent.class); 2063 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appButton3Node)); 2064 when(event.getEventType()).thenReturn(TYPE_VIEW_CLICKED); 2065 when(event.getEventTime()).thenReturn(-1l); 2066 mRotaryService.onAccessibilityEvent(event); 2067 2068 assertThat(mRotaryService.getFocusedNode()).isNull(); 2069 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 2070 assertThat(mRotaryService.mLastTouchedNode).isEqualTo(appButton3Node); 2071 } 2072 2073 @Test testOnAccessibilityEvent_typeWindowStateChanged()2074 public void testOnAccessibilityEvent_typeWindowStateChanged() { 2075 AccessibilityWindowInfo window = mock(AccessibilityWindowInfo.class); 2076 when(window.getType()).thenReturn(TYPE_APPLICATION); 2077 when(window.isFocused()).thenReturn(true); 2078 when(window.getDisplayId()).thenReturn(DEFAULT_DISPLAY); 2079 2080 AccessibilityNodeInfo node = mock(AccessibilityNodeInfo.class); 2081 when(node.getWindow()).thenReturn(window); 2082 2083 AccessibilityEvent event = mock(AccessibilityEvent.class); 2084 when(event.getSource()).thenReturn(node); 2085 when(event.getEventType()).thenReturn(TYPE_WINDOW_STATE_CHANGED); 2086 final String packageName = "package.name"; 2087 final String className = "class.name"; 2088 when(event.getPackageName()).thenReturn(packageName); 2089 when(event.getClassName()).thenReturn(className); 2090 mRotaryService.onAccessibilityEvent(event); 2091 2092 ComponentName foregroundActivity = new ComponentName(packageName, className); 2093 assertThat(mRotaryService.mForegroundActivity).isEqualTo(foregroundActivity); 2094 } 2095 2096 /** 2097 * Tests Direct Manipulation mode in the following view tree: 2098 * <pre> 2099 * The HUN window: 2100 * 2101 * hun FocusParkingView 2102 * ==========HUN focus area========== 2103 * = = 2104 * = ............. ............. = 2105 * = . . . . = 2106 * = .hun button1. .hun button2. = 2107 * = . . . . = 2108 * = ............. ............. = 2109 * = = 2110 * ================================== 2111 * 2112 * The app window: 2113 * 2114 * app FocusParkingView 2115 * ===========focus area 1=========== ===========focus area 2=========== 2116 * = = = = 2117 * = ............. ............. = = ............. ............. = 2118 * = . . . . = = . . . . = 2119 * = .app button1. . nudge . = = .app button2. . nudge . = 2120 * = . . . shortcut1 . = = . . . shortcut2 . = 2121 * = ............. ............. = = ............. ............. = 2122 * = = = = 2123 * ================================== ================================== 2124 * 2125 * ===========focus area 3=========== 2126 * = = 2127 * = ............. ............. = 2128 * = . . . . = 2129 * = .app button3. . default . = 2130 * = . (focused) . . focus . = 2131 * = ............. ............. = 2132 * = = 2133 * ================================== 2134 * </pre> 2135 */ 2136 @Test testDirectManipulationMode1()2137 public void testDirectManipulationMode1() { 2138 initActivity(R.layout.rotary_service_test_2_activity); 2139 2140 Activity activity = mActivityRule.getActivity(); 2141 Button appButton3 = activity.findViewById(R.id.app_button3); 2142 DirectManipulationHelper.setSupportsRotateDirectly(appButton3, true); 2143 appButton3.post(() -> appButton3.requestFocus()); 2144 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 2145 assertThat(appButton3.isFocused()).isTrue(); 2146 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 2147 mRotaryService.setFocusedNode(appButton3Node); 2148 mRotaryService.mInRotaryMode = true; 2149 assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); 2150 assertThat(appButton3.isSelected()).isFalse(); 2151 2152 // Click the center button of the controller. 2153 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 2154 KeyEvent centerButtonEventActionDown = 2155 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 2156 mRotaryService.onKeyEvents(validDisplayId, 2157 Collections.singletonList(centerButtonEventActionDown)); 2158 KeyEvent centerButtonEventActionUp = 2159 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 2160 mRotaryService.onKeyEvents(validDisplayId, 2161 Collections.singletonList(centerButtonEventActionUp)); 2162 2163 // RotaryService should enter Direct Manipulation mode because appButton3Node 2164 // supports rotate directly. 2165 assertThat(mRotaryService.mInDirectManipulationMode).isTrue(); 2166 assertThat(appButton3.isSelected()).isTrue(); 2167 2168 // Click the back button of the controller. 2169 KeyEvent backButtonEventActionDown = 2170 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 2171 mRotaryService.onKeyEvents(validDisplayId, 2172 Collections.singletonList(backButtonEventActionDown)); 2173 KeyEvent backButtonEventActionUp = 2174 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 2175 mRotaryService.onKeyEvents(validDisplayId, 2176 Collections.singletonList(backButtonEventActionUp)); 2177 2178 // RotaryService should exit Direct Manipulation mode because appButton3Node 2179 // supports rotate directly. 2180 assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); 2181 assertThat(appButton3.isSelected()).isFalse(); 2182 } 2183 2184 /** 2185 * Tests Direct Manipulation mode in the following view tree: 2186 * <pre> 2187 * The HUN window: 2188 * 2189 * hun FocusParkingView 2190 * ==========HUN focus area========== 2191 * = = 2192 * = ............. ............. = 2193 * = . . . . = 2194 * = .hun button1. .hun button2. = 2195 * = . . . . = 2196 * = ............. ............. = 2197 * = = 2198 * ================================== 2199 * 2200 * The app window: 2201 * 2202 * app FocusParkingView 2203 * ===========focus area 1=========== ===========focus area 2=========== 2204 * = = = = 2205 * = ............. ............. = = ............. ............. = 2206 * = . . . . = = . . . . = 2207 * = .app button1. . nudge . = = .app button2. . nudge . = 2208 * = . . . shortcut1 . = = . . . shortcut2 . = 2209 * = ............. ............. = = ............. ............. = 2210 * = = = = 2211 * ================================== ================================== 2212 * 2213 * ===========focus area 3=========== 2214 * = = 2215 * = ............. ............. = 2216 * = . . . . = 2217 * = .app button3. . default . = 2218 * = . (focused) . . focus . = 2219 * = ............. ............. = 2220 * = = 2221 * ================================== 2222 * </pre> 2223 */ 2224 @Test testDirectManipulationMode2()2225 public void testDirectManipulationMode2() { 2226 initActivity(R.layout.rotary_service_test_2_activity); 2227 2228 Activity activity = mActivityRule.getActivity(); 2229 Button appButton3 = activity.findViewById(R.id.app_button3); 2230 appButton3.post(() -> appButton3.requestFocus()); 2231 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 2232 assertThat(appButton3.isFocused()).isTrue(); 2233 AccessibilityNodeInfo appButton3Node = createNode("app_button3"); 2234 mRotaryService.setFocusedNode(appButton3Node); 2235 mRotaryService.mInRotaryMode = true; 2236 when(mRotaryService.isInFocusedWindow(appButton3Node)).thenReturn(true); 2237 assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); 2238 assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); 2239 2240 // Click the center button of the controller. 2241 int validDisplayId = CarOccupantZoneManager.DISPLAY_TYPE_MAIN; 2242 KeyEvent centerButtonEventActionDown = 2243 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); 2244 mRotaryService.onKeyEvents(validDisplayId, 2245 Collections.singletonList(centerButtonEventActionDown)); 2246 KeyEvent centerButtonEventActionUp = 2247 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); 2248 mRotaryService.onKeyEvents(validDisplayId, 2249 Collections.singletonList(centerButtonEventActionUp)); 2250 2251 // RotaryService should inject KEYCODE_DPAD_CENTER event because appButton3Node doesn't 2252 // support rotate directly and is in the application window. 2253 verify(mRotaryService, times(1)) 2254 .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_DOWN); 2255 verify(mRotaryService, times(1)) 2256 .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_UP); 2257 assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(appButton3Node); 2258 2259 // The app sends a TYPE_VIEW_ACCESSIBILITY_FOCUSED event to RotaryService. 2260 // RotaryService should enter Direct Manipulation mode when receiving the event. 2261 AccessibilityEvent event = mock(AccessibilityEvent.class); 2262 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appButton3Node)); 2263 when(event.getEventType()).thenReturn(TYPE_VIEW_ACCESSIBILITY_FOCUSED); 2264 when(event.getClassName()).thenReturn(DIRECT_MANIPULATION); 2265 mRotaryService.onAccessibilityEvent(event); 2266 assertThat(mRotaryService.mInDirectManipulationMode).isTrue(); 2267 2268 // Click the back button of the controller. 2269 KeyEvent backButtonEventActionDown = 2270 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); 2271 mRotaryService.onKeyEvents(validDisplayId, 2272 Collections.singletonList(backButtonEventActionDown)); 2273 KeyEvent backButtonEventActionUp = 2274 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); 2275 mRotaryService.onKeyEvents(validDisplayId, 2276 Collections.singletonList(backButtonEventActionUp)); 2277 2278 // RotaryService should inject KEYCODE_BACK event because appButton3Node doesn't 2279 // support rotate directly and is in the application window. 2280 verify(mRotaryService, times(1)) 2281 .injectKeyEvent(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_DOWN); 2282 verify(mRotaryService, times(1)) 2283 .injectKeyEvent(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_UP); 2284 2285 // The app sends a TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED event to RotaryService. 2286 // RotaryService should exit Direct Manipulation mode when receiving the event. 2287 event = mock(AccessibilityEvent.class); 2288 when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appButton3Node)); 2289 when(event.getEventType()).thenReturn(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 2290 when(event.getClassName()).thenReturn(DIRECT_MANIPULATION); 2291 mRotaryService.onAccessibilityEvent(event); 2292 assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); 2293 } 2294 2295 /** 2296 * Starts the test activity with the given layout and initializes the root 2297 * {@link AccessibilityNodeInfo}. 2298 */ initActivity(@ayoutRes int layoutResId)2299 private void initActivity(@LayoutRes int layoutResId) { 2300 mIntent.putExtra(NavigatorTestActivity.KEY_LAYOUT_ID, layoutResId); 2301 mActivityRule.launchActivity(mIntent); 2302 mWindowRoot = sUiAutomation.getRootInActiveWindow(); 2303 } 2304 2305 /** 2306 * Returns the {@link AccessibilityNodeInfo} related to the provided {@code viewId}. Returns 2307 * null if no such node exists. Callers should ensure {@link #initActivity} has already been 2308 * called. Caller shouldn't recycle the result because it will be recycled in {@link #tearDown}. 2309 */ createNode(String viewId)2310 private AccessibilityNodeInfo createNode(String viewId) { 2311 String fullViewId = "com.android.car.rotary.tests.unit:id/" + viewId; 2312 List<AccessibilityNodeInfo> nodes = 2313 mWindowRoot.findAccessibilityNodeInfosByViewId(fullViewId); 2314 if (nodes.isEmpty()) { 2315 L.e("Failed to create node by View ID " + viewId); 2316 return null; 2317 } 2318 mNodes.addAll(nodes); 2319 return nodes.get(0); 2320 } 2321 } 2322