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