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