aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-03-18 22:05:41 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-03-18 22:05:41 +0000
commitb7d58c94f2dce9a3c85bf4cb838fc167066ced72 (patch)
tree3f71bc1b64b861701ed921e410ec8c816c583b1a
parentd96977f208afc3f76a555b163f63646cdd05e4a8 (diff)
parent7eb95eb7a0cdbf559dfafcd95b4f22d1783279e4 (diff)
downloadplatform_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
-rw-r--r--src/com/android/car/rotary/RotaryService.java6
-rw-r--r--tests/unit/src/com/android/car/rotary/NodeBuilder.java11
-rw-r--r--tests/unit/src/com/android/car/rotary/NodeBuilderTest.java8
-rw-r--r--tests/unit/src/com/android/car/rotary/RotaryServiceTest.java203
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}.
*/