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