diff options
| author | android-build-team Robot <android-build-team-robot@google.com> | 2021-03-18 22:05:41 +0000 |
|---|---|---|
| committer | android-build-team Robot <android-build-team-robot@google.com> | 2021-03-18 22:05:41 +0000 |
| commit | b7d58c94f2dce9a3c85bf4cb838fc167066ced72 (patch) | |
| tree | 3f71bc1b64b861701ed921e410ec8c816c583b1a | |
| parent | d96977f208afc3f76a555b163f63646cdd05e4a8 (diff) | |
| parent | 7eb95eb7a0cdbf559dfafcd95b4f22d1783279e4 (diff) | |
| download | platform_packages_apps_Car_RotaryController-b7d58c94f2dce9a3c85bf4cb838fc167066ced72.tar.gz platform_packages_apps_Car_RotaryController-b7d58c94f2dce9a3c85bf4cb838fc167066ced72.tar.bz2 platform_packages_apps_Car_RotaryController-b7d58c94f2dce9a3c85bf4cb838fc167066ced72.zip | |
Snap for 7218778 from 7eb95eb7a0cdbf559dfafcd95b4f22d1783279e4 to rvc-qpr3-release
Change-Id: I7f3c4d159ecce68479b432c5137db6830566cf37
4 files changed, 226 insertions, 2 deletions
diff --git a/src/com/android/car/rotary/RotaryService.java b/src/com/android/car/rotary/RotaryService.java index 26267bb..8e5e91c 100644 --- a/src/com/android/car/rotary/RotaryService.java +++ b/src/com/android/car/rotary/RotaryService.java @@ -391,7 +391,8 @@ public class RotaryService extends AccessibilityService implements * this mode is controlled by the client app, which is responsible for updating the mode by * calling {@link DirectManipulationHelper#enableDirectManipulationMode} when needed. */ - private boolean mInDirectManipulationMode; + @VisibleForTesting + boolean mInDirectManipulationMode; /** The {@link SystemClock#uptimeMillis} when the last rotary rotation event occurred. */ private long mLastRotateEventTime; @@ -1739,7 +1740,8 @@ public class RotaryService extends AccessibilityService implements } /** Returns whether the given {@code node} is in the application window. */ - private static boolean isInApplicationWindow(@NonNull AccessibilityNodeInfo node) { + @VisibleForTesting + boolean isInApplicationWindow(@NonNull AccessibilityNodeInfo node) { AccessibilityWindowInfo window = node.getWindow(); if (window == null) { L.w("Failed to get window of " + node); diff --git a/tests/unit/src/com/android/car/rotary/NodeBuilder.java b/tests/unit/src/com/android/car/rotary/NodeBuilder.java index 9cd3847..1578aba 100644 --- a/tests/unit/src/com/android/car/rotary/NodeBuilder.java +++ b/tests/unit/src/com/android/car/rotary/NodeBuilder.java @@ -95,6 +95,9 @@ class NodeBuilder { /** The content description for this node. */ @Nullable private String mContentDescription; + /** The state description for this node. */ + @Nullable + private String mStateDescription; /** The action list for this node. */ @NonNull private List<AccessibilityNodeInfo.AccessibilityAction> mActionList = new ArrayList<>(); @@ -195,6 +198,7 @@ class NodeBuilder { when(node.refresh()).thenReturn(builder.mInViewTree); when(node.isScrollable()).thenReturn(builder.mScrollable); when(node.getContentDescription()).thenReturn(builder.mContentDescription); + when(node.getStateDescription()).thenReturn(builder.mStateDescription); when(node.getActionList()).thenReturn(builder.mActionList); when(node.getExtras()).thenReturn(builder.mExtras); builder.mNodeList.add(node); @@ -273,6 +277,11 @@ class NodeBuilder { return this; } + NodeBuilder setStateDescription(@Nullable String stateDescription) { + mStateDescription = stateDescription; + return this; + } + NodeBuilder setActions(AccessibilityNodeInfo.AccessibilityAction... actions) { mActionList = Arrays.asList(actions); return this; @@ -327,6 +336,7 @@ class NodeBuilder { copy.mInViewTree = mInViewTree; copy.mScrollable = mScrollable; copy.mContentDescription = mContentDescription; + copy.mStateDescription = mStateDescription; copy.mActionList = mActionList; copy.mExtras = mExtras; // Clear the states so that it doesn't infect the next NodeBuilder we create. @@ -344,6 +354,7 @@ class NodeBuilder { mInViewTree = true; mScrollable = false; mContentDescription = null; + mStateDescription = null; mActionList = new ArrayList<>(); mExtras = new Bundle(); return copy; diff --git a/tests/unit/src/com/android/car/rotary/NodeBuilderTest.java b/tests/unit/src/com/android/car/rotary/NodeBuilderTest.java index eaea27c..0314646 100644 --- a/tests/unit/src/com/android/car/rotary/NodeBuilderTest.java +++ b/tests/unit/src/com/android/car/rotary/NodeBuilderTest.java @@ -48,6 +48,7 @@ public class NodeBuilderTest { private static final String PACKAGE_NAME = "package_name"; private static final String CLASS_NAME = "class_name"; private static final String CONTENT_DESCRIPTION = "content_description"; + private static final String STATE_DESCRIPTION = "state_description"; private NodeBuilder mNodeBuilder; @Before @@ -154,6 +155,13 @@ public class NodeBuilderTest { } @Test + public void testSetStateDescription() { + AccessibilityNodeInfo node = + mNodeBuilder.setStateDescription(STATE_DESCRIPTION).build(); + assertThat(node.getStateDescription().toString()).isEqualTo(STATE_DESCRIPTION); + } + + @Test public void testSetParent() { AccessibilityNodeInfo parent = mNodeBuilder.build(); AccessibilityNodeInfo child1 = mNodeBuilder.setParent(parent).build(); diff --git a/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java b/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java index 7fccae5..2022600 100644 --- a/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java +++ b/tests/unit/src/com/android/car/rotary/RotaryServiceTest.java @@ -17,11 +17,14 @@ package com.android.car.rotary; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED; +import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; import static android.view.accessibility.AccessibilityWindowInfo.TYPE_APPLICATION; +import static com.android.car.ui.utils.DirectManipulationHelper.DIRECT_MANIPULATION; import static com.android.car.ui.utils.RotaryConstants.ACTION_RESTORE_DEFAULT_FOCUS; import static com.google.common.truth.Truth.assertThat; @@ -55,6 +58,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.car.ui.FocusParkingView; +import com.android.car.ui.utils.DirectManipulationHelper; import org.junit.After; import org.junit.Before; @@ -1722,6 +1726,205 @@ public class RotaryServiceTest { } /** + * Tests Direct Manipulation mode in the following view tree: + * <pre> + * The HUN window: + * + * hun FocusParkingView + * ==========HUN focus area========== + * = = + * = ............. ............. = + * = . . . . = + * = .hun button1. .hun button2. = + * = . . . . = + * = ............. ............. = + * = = + * ================================== + * + * The app window: + * + * app FocusParkingView + * ===========focus area 1=========== ============focus area 2=========== + * = = = = + * = ............. ............. = = ............. = + * = . . . . = = . . = + * = .app button1. . nudge . = = .app button2. = + * = . . . shortcut . = = . . = + * = ............. ............. = = ............. = + * = = = = + * ================================== =================================== + * + * ===========focus area 3=========== + * = = + * = ............. ............. = + * = . . . . = + * = .app button3. . default . = + * = . (focused) . . focus . = + * = ............. ............. = + * = = + * ================================== + * </pre> + */ + @Test + public void testDirectManipulationMode1() { + initActivity(R.layout.rotary_service_test_2_activity); + + Activity activity = mActivityRule.getActivity(); + Button appButton3 = activity.findViewById(R.id.app_button3); + DirectManipulationHelper.setSupportsRotateDirectly(appButton3, true); + appButton3.post(() -> appButton3.requestFocus()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + assertThat(appButton3.isFocused()).isTrue(); + AccessibilityNodeInfo appButton3Node = createNode("app_button3"); + mRotaryService.setFocusedNode(appButton3Node); + mRotaryService.mInRotaryMode = true; + assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); + assertThat(appButton3.isSelected()).isFalse(); + + // Click the center button of the controller. + int validDisplayId = CarInputManager.TARGET_DISPLAY_TYPE_MAIN; + KeyEvent centerButtonEventActionDown = + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(centerButtonEventActionDown)); + KeyEvent centerButtonEventActionUp = + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(centerButtonEventActionUp)); + + // RotaryService should enter Direct Manipulation mode because appButton3Node + // supports rotate directly. + assertThat(mRotaryService.mInDirectManipulationMode).isTrue(); + assertThat(appButton3.isSelected()).isTrue(); + + // Click the back button of the controller. + KeyEvent backButtonEventActionDown = + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(backButtonEventActionDown)); + KeyEvent backButtonEventActionUp = + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(backButtonEventActionUp)); + + // RotaryService should exit Direct Manipulation mode because appButton3Node + // supports rotate directly. + assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); + assertThat(appButton3.isSelected()).isFalse(); + } + + /** + * Tests Direct Manipulation mode in the following view tree: + * <pre> + * The HUN window: + * + * hun FocusParkingView + * ==========HUN focus area========== + * = = + * = ............. ............. = + * = . . . . = + * = .hun button1. .hun button2. = + * = . . . . = + * = ............. ............. = + * = = + * ================================== + * + * The app window: + * + * app FocusParkingView + * ===========focus area 1=========== ============focus area 2=========== + * = = = = + * = ............. ............. = = ............. = + * = . . . . = = . . = + * = .app button1. . nudge . = = .app button2. = + * = . . . shortcut . = = . . = + * = ............. ............. = = ............. = + * = = = = + * ================================== =================================== + * + * ===========focus area 3=========== + * = = + * = ............. ............. = + * = . . . . = + * = .app button3. . default . = + * = . (focused) . . focus . = + * = ............. ............. = + * = = + * ================================== + * </pre> + */ + @Test + public void testDirectManipulationMode2() { + initActivity(R.layout.rotary_service_test_2_activity); + + Activity activity = mActivityRule.getActivity(); + Button appButton3 = activity.findViewById(R.id.app_button3); + appButton3.post(() -> appButton3.requestFocus()); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + assertThat(appButton3.isFocused()).isTrue(); + AccessibilityNodeInfo appButton3Node = createNode("app_button3"); + mRotaryService.setFocusedNode(appButton3Node); + mRotaryService.mInRotaryMode = true; + when(mRotaryService.isInApplicationWindow(appButton3Node)).thenReturn(true); + assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); + assertThat(mRotaryService.mIgnoreViewClickedNode).isNull(); + + // Click the center button of the controller. + int validDisplayId = CarInputManager.TARGET_DISPLAY_TYPE_MAIN; + KeyEvent centerButtonEventActionDown = + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(centerButtonEventActionDown)); + KeyEvent centerButtonEventActionUp = + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(centerButtonEventActionUp)); + + // RotaryService should inject KEYCODE_DPAD_CENTER event because appButton3Node doesn't + // support rotate directly and is in the application window. + verify(mRotaryService, times(1)) + .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_DOWN); + verify(mRotaryService, times(1)) + .injectKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.ACTION_UP); + assertThat(mRotaryService.mIgnoreViewClickedNode).isEqualTo(appButton3Node); + + // The app sends a TYPE_VIEW_ACCESSIBILITY_FOCUSED event to RotaryService. + // RotaryService should enter Direct Manipulation mode when receiving the event. + AccessibilityEvent event = mock(AccessibilityEvent.class); + when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appButton3Node)); + when(event.getEventType()).thenReturn(TYPE_VIEW_ACCESSIBILITY_FOCUSED); + when(event.getClassName()).thenReturn(DIRECT_MANIPULATION); + mRotaryService.onAccessibilityEvent(event); + assertThat(mRotaryService.mInDirectManipulationMode).isTrue(); + + // Click the back button of the controller. + KeyEvent backButtonEventActionDown = + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(backButtonEventActionDown)); + KeyEvent backButtonEventActionUp = + new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); + mRotaryService.onKeyEvents(validDisplayId, + Collections.singletonList(backButtonEventActionUp)); + + // RotaryService should inject KEYCODE_BACK event because appButton3Node doesn't + // support rotate directly and is in the application window. + verify(mRotaryService, times(1)) + .injectKeyEvent(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_DOWN); + verify(mRotaryService, times(1)) + .injectKeyEvent(KeyEvent.KEYCODE_BACK, KeyEvent.ACTION_UP); + + // The app sends a TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED event to RotaryService. + // RotaryService should exit Direct Manipulation mode when receiving the event. + event = mock(AccessibilityEvent.class); + when(event.getSource()).thenReturn(AccessibilityNodeInfo.obtain(appButton3Node)); + when(event.getEventType()).thenReturn(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); + when(event.getClassName()).thenReturn(DIRECT_MANIPULATION); + mRotaryService.onAccessibilityEvent(event); + assertThat(mRotaryService.mInDirectManipulationMode).isFalse(); + } + + /** * Starts the test activity with the given layout and initializes the root * {@link AccessibilityNodeInfo}. */ |
