aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-04-07 02:30:50 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-04-07 02:30:50 +0000
commitab49282dabd2c2866a005ff379f0beb4ac2d4c97 (patch)
treec83d39b6e06a554ece845da3fb4834591c5961b0
parentba529970c8b25cc7fcfb0b55874c6dfd90407efc (diff)
parent7e1aa85f0b8338dec231743c9218aecfe31d2081 (diff)
downloadtests-ab49282dabd2c2866a005ff379f0beb4ac2d4c97.tar.gz
Snap for 7192656 from 3d45fea12a911a1de3840b640a859b93263b492c to rvc-platform-release am: 7e1aa85f0b
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/tests/+/13876941 Change-Id: I36aaa57456aca5d7ca2fd1899ca99609de3b7b76
-rw-r--r--RotaryPlayground/res/layout/rotary_scroll.xml9
-rw-r--r--RotaryPlayground/res/layout/rotary_web_view.xml3
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationHandler.java70
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationState.java52
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java52
5 files changed, 111 insertions, 75 deletions
diff --git a/RotaryPlayground/res/layout/rotary_scroll.xml b/RotaryPlayground/res/layout/rotary_scroll.xml
index 4153ca1..ed550cd 100644
--- a/RotaryPlayground/res/layout/rotary_scroll.xml
+++ b/RotaryPlayground/res/layout/rotary_scroll.xml
@@ -17,6 +17,7 @@
<com.android.car.ui.FocusArea
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -26,11 +27,17 @@
android:layout_height="wrap_content"
android:singleLine="true"/>
+ <!-- Use contentDescription to enable rotary scrolling for this view. With rotary scrolling
+ enabled, rotation will scroll rather than moving the focus if moving the focus would
+ cause a lot of scrolling. Rotary scrolling should be enabled for scrolling views which
+ contain content which the user may want to see but can't interact with, either alone or
+ with interactive (focusable) content. -->
<com.android.car.ui.recyclerview.CarUiRecyclerView
android:id="@+id/rotary_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ app:rotaryScrollEnabled="true"/>
<EditText
android:layout_width="match_parent"
diff --git a/RotaryPlayground/res/layout/rotary_web_view.xml b/RotaryPlayground/res/layout/rotary_web_view.xml
index 7995dc6..6cdc5dd 100644
--- a/RotaryPlayground/res/layout/rotary_web_view.xml
+++ b/RotaryPlayground/res/layout/rotary_web_view.xml
@@ -51,7 +51,8 @@
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:defaultFocusHighlightEnabled="false"/>
<Button
android:id="@+id/bottom_button"
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationHandler.java b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationHandler.java
index fc06754..29445c5 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationHandler.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationHandler.java
@@ -54,9 +54,18 @@ import androidx.core.util.Preconditions;
public class DirectManipulationHandler implements View.OnKeyListener,
View.OnGenericMotionListener {
- private final DirectManipulationState mDirectManipulationMode;
- private final View.OnKeyListener mNudgeDelegate;
- private final View.OnGenericMotionListener mRotationDelegate;
+ /**
+ * Sets the provided {@link DirectManipulationHandler} to the key listener and motion
+ * listener of the provided view.
+ */
+ public static void setDirectManipulationHandler(@Nullable View view,
+ DirectManipulationHandler handler) {
+ if (view == null) {
+ return;
+ }
+ view.setOnKeyListener(handler);
+ view.setOnGenericMotionListener(handler);
+ }
/**
* A builder for {@link DirectManipulationHandler}.
@@ -65,45 +74,59 @@ public class DirectManipulationHandler implements View.OnKeyListener,
private final DirectManipulationState mDmState;
private View.OnKeyListener mNudgeDelegate;
private View.OnGenericMotionListener mRotationDelegate;
+ private View.OnKeyListener mBackDelegate;
public Builder(DirectManipulationState dmState) {
Preconditions.checkNotNull(dmState);
this.mDmState = dmState;
}
- public Builder setNudgeHandler(View.OnKeyListener directionalDelegate) {
- Preconditions.checkNotNull(directionalDelegate);
- this.mNudgeDelegate = directionalDelegate;
+ public Builder setNudgeHandler(View.OnKeyListener nudgeDelegate) {
+ Preconditions.checkNotNull(nudgeDelegate);
+ this.mNudgeDelegate = nudgeDelegate;
+ return this;
+ }
+
+ public Builder setBackHandler(View.OnKeyListener backDelegate) {
+ Preconditions.checkNotNull(backDelegate);
+ this.mBackDelegate = backDelegate;
return this;
}
- public Builder setRotationHandler(View.OnGenericMotionListener motionDelegate) {
- Preconditions.checkNotNull(motionDelegate);
- this.mRotationDelegate = motionDelegate;
+ public Builder setRotationHandler(View.OnGenericMotionListener rotationDelegate) {
+ Preconditions.checkNotNull(rotationDelegate);
+ this.mRotationDelegate = rotationDelegate;
return this;
}
public DirectManipulationHandler build() {
if (mNudgeDelegate == null && mRotationDelegate == null) {
- throw new IllegalStateException("At least one delegate must be provided.");
+ throw new IllegalStateException("Nudge and/or rotation delegate must be provided.");
}
- return new DirectManipulationHandler(mDmState, mNudgeDelegate, mRotationDelegate);
+ return new DirectManipulationHandler(mDmState, mNudgeDelegate, mBackDelegate,
+ mRotationDelegate);
}
}
+ private final DirectManipulationState mDirectManipulationMode;
+ private final View.OnKeyListener mNudgeDelegate;
+ private final View.OnKeyListener mBackDelegate;
+ private final View.OnGenericMotionListener mRotationDelegate;
+
private DirectManipulationHandler(DirectManipulationState dmState,
@Nullable View.OnKeyListener nudgeDelegate,
+ @Nullable View.OnKeyListener backDelegate,
@Nullable View.OnGenericMotionListener rotationDelegate) {
- Preconditions.checkNotNull(dmState);
mDirectManipulationMode = dmState;
mNudgeDelegate = nudgeDelegate;
+ mBackDelegate = backDelegate;
mRotationDelegate = rotationDelegate;
}
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
- Log.d(L.TAG, "View: " + view + " is handling " + keyCode
+ Log.d(L.TAG, "View: " + view + " is handling " + KeyEvent.keyCodeToString(keyCode)
+ " and action " + keyEvent.getAction()
+ " direct manipulation mode is "
+ (mDirectManipulationMode.isActive() ? "active" : "inactive"));
@@ -121,18 +144,29 @@ public class DirectManipulationHandler implements View.OnKeyListener,
if (mDirectManipulationMode.isActive() && isActionUp) {
mDirectManipulationMode.disable();
}
- return true;
- default:
- // This handler is only responsible for behavior during Direct Manipulation
+ // If no delegate is present, silently consume the events.
+ if (mBackDelegate == null) {
+ return true;
+ }
+
+ return mBackDelegate.onKey(view, keyCode, keyEvent);
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ // This handler is only responsible for nudging behavior during Direct Manipulation
// mode. When the mode is disabled, ignore events.
if (!mDirectManipulationMode.isActive()) {
return false;
}
- // If no delegate present, silently consume the events.
+ // If no delegate is present, silently consume the events.
if (mNudgeDelegate == null) {
return true;
}
return mNudgeDelegate.onKey(view, keyCode, keyEvent);
+ default:
+ // Ignore all other key events.
+ return false;
}
}
@@ -143,7 +177,7 @@ public class DirectManipulationHandler implements View.OnKeyListener,
if (!mDirectManipulationMode.isActive()) {
return false;
}
- // If no delegate present, silently consume the events.
+ // If no delegate is present, silently consume the events.
if (mRotationDelegate == null) {
return true;
}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationState.java b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationState.java
index 05d236b..a802c71 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationState.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/DirectManipulationState.java
@@ -16,7 +16,10 @@
package com.android.car.rotaryplayground;
+import static android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS;
+
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
@@ -34,19 +37,20 @@ import com.android.car.ui.utils.DirectManipulationHelper;
*/
public class DirectManipulationState {
-
/** Background color of a view when it's in direct manipulation mode. */
private static final int BACKGROUND_COLOR_IN_DIRECT_MANIPULATION_MODE = Color.BLUE;
- /** Background color of a view when it's not in direct manipulation mode. */
- private static final int BACKGROUND_COLOR_NOT_IN_DIRECT_MANIPULATION_MODE = Color.TRANSPARENT;
+ /** Indicates that the descendant focusability has not been set. */
+ private static final int UNKNOWN_DESCENDANT_FOCUSABILITY = -1;
/** The view that is in direct manipulation mode, or null if none. */
- @Nullable private View mViewInDirectManipulationMode;
-
- private void setStartingView(@Nullable View view) {
- mViewInDirectManipulationMode = view;
- }
+ @Nullable
+ private View mViewInDirectManipulationMode;
+ /** The original background of the view in direct manipulation mode. */
+ @Nullable
+ private Drawable mOriginalBackground;
+ /** The original descendant focusability value of the view in direct manipulation mode. */
+ private int mOriginalDescendantFocusability = UNKNOWN_DESCENDANT_FOCUSABILITY;
/**
* Returns true if Direct Manipulation mode is active, false otherwise.
@@ -62,19 +66,18 @@ public class DirectManipulationState {
* We generally want to give some kind of visual indication that this change has happened. In
* this example we change the background color of {@code view}.
*
- * @param view - the {@link View} from which we entered into Direct Manipulation mode.
+ * @param view the {@link View} from which we entered into Direct Manipulation mode
*/
public void enable(@NonNull View view) {
- /*
- * A more robust approach would be to fetch the current background color from
- * the view object and store it back onto the View itself using the {@link
- * View#setTag(int, java.lang.Object)} API. This could then be fetched back
- * and used to restore the background color without needing to keep a constant
- * reference to the color here which could fall out of sync with the xml files.
- */
+ mViewInDirectManipulationMode = view;
+ mOriginalBackground = view.getBackground();
+ if (mViewInDirectManipulationMode instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) mViewInDirectManipulationMode;
+ mOriginalDescendantFocusability = viewGroup.getDescendantFocusability();
+ viewGroup.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ }
view.setBackgroundColor(BACKGROUND_COLOR_IN_DIRECT_MANIPULATION_MODE);
DirectManipulationHelper.enableDirectManipulationMode(view, /* enable= */ true);
- setStartingView(view);
}
/**
@@ -82,17 +85,18 @@ public class DirectManipulationState {
* from which we entered into Direct Manipulation mode.
*/
public void disable() {
- mViewInDirectManipulationMode.setBackgroundColor(
- BACKGROUND_COLOR_NOT_IN_DIRECT_MANIPULATION_MODE);
+ mViewInDirectManipulationMode.setBackground(mOriginalBackground);
DirectManipulationHelper.enableDirectManipulationMode(
mViewInDirectManipulationMode, /* enable= */ false);
- // For ViewGroup objects, restore descendant focusability to FOCUS_BLOCK_DESCENDANTS so
- // during non-Direct Manipulation mode, aka, general rotary navigation, we don't go
- // through the individual inner UI elements.
- if (mViewInDirectManipulationMode instanceof ViewGroup) {
+ // For ViewGroup objects, restore descendant focusability to the previous value.
+ if (mViewInDirectManipulationMode instanceof ViewGroup
+ && mOriginalDescendantFocusability != UNKNOWN_DESCENDANT_FOCUSABILITY) {
ViewGroup viewGroup = (ViewGroup) mViewInDirectManipulationMode;
viewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
- setStartingView(null);
+
+ mViewInDirectManipulationMode = null;
+ mOriginalBackground = null;
+ mOriginalDescendantFocusability = UNKNOWN_DESCENDANT_FOCUSABILITY;
}
}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
index 9184597..68d7d0f 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryDirectManipulationWidgets.java
@@ -16,6 +16,8 @@
package com.android.car.rotaryplayground;
+import static com.android.car.rotaryplayground.DirectManipulationHandler.setDirectManipulationHandler;
+
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -55,7 +57,7 @@ public class RotaryDirectManipulationWidgets extends Fragment {
View view = inflater.inflate(R.layout.rotary_direct_manipulation, container, false);
DirectManipulationView dmv = view.findViewById(R.id.direct_manipulation_view);
- registerDirectManipulationHandler(dmv,
+ setDirectManipulationHandler(dmv,
new DirectManipulationHandler.Builder(mDirectManipulationMode)
.setNudgeHandler(new DirectManipulationView.NudgeHandler())
.setRotationHandler(new DirectManipulationView.RotationHandler())
@@ -63,7 +65,7 @@ public class RotaryDirectManipulationWidgets extends Fragment {
TimePicker spinnerTimePicker = view.findViewById(R.id.spinner_time_picker);
- registerDirectManipulationHandler(spinnerTimePicker,
+ setDirectManipulationHandler(spinnerTimePicker,
new DirectManipulationHandler.Builder(mDirectManipulationMode)
.setNudgeHandler(new TimePickerNudgeHandler())
.build());
@@ -71,11 +73,15 @@ public class RotaryDirectManipulationWidgets extends Fragment {
DirectManipulationHandler numberPickerListener =
new DirectManipulationHandler.Builder(mDirectManipulationMode)
.setNudgeHandler(new NumberPickerNudgeHandler())
+ .setBackHandler((v, keyCode, event) -> {
+ spinnerTimePicker.requestFocus();
+ return true;
+ })
.setRotationHandler((v, motionEvent) -> {
- float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
View focusedView = v.findFocus();
if (focusedView instanceof NumberPicker) {
NumberPicker numberPicker = (NumberPicker) focusedView;
+ float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL);
numberPicker.setValue(numberPicker.getValue() + Math.round(scroll));
return true;
}
@@ -86,10 +92,10 @@ public class RotaryDirectManipulationWidgets extends Fragment {
List<NumberPicker> numberPickers = new ArrayList<>();
getNumberPickerDescendants(numberPickers, spinnerTimePicker);
for (int i = 0; i < numberPickers.size(); i++) {
- registerDirectManipulationHandler(numberPickers.get(i), numberPickerListener);
+ setDirectManipulationHandler(numberPickers.get(i), numberPickerListener);
}
- registerDirectManipulationHandler(view.findViewById(R.id.clock_time_picker),
+ setDirectManipulationHandler(view.findViewById(R.id.clock_time_picker),
new DirectManipulationHandler.Builder(
mDirectManipulationMode)
// TODO(pardis): fix the behavior here. It does not nudge as expected.
@@ -103,13 +109,13 @@ public class RotaryDirectManipulationWidgets extends Fragment {
})
.build());
- registerDirectManipulationHandler(
+ setDirectManipulationHandler(
view.findViewById(R.id.seek_bar),
new DirectManipulationHandler.Builder(mDirectManipulationMode)
.setRotationHandler(new DelegateToA11yScrollRotationHandler())
.build());
- registerDirectManipulationHandler(
+ setDirectManipulationHandler(
view.findViewById(R.id.radial_time_picker),
new DirectManipulationHandler.Builder(mDirectManipulationMode)
.setRotationHandler(new DelegateToA11yScrollRotationHandler())
@@ -129,23 +135,6 @@ public class RotaryDirectManipulationWidgets extends Fragment {
}
/**
- * Register the given {@link DirectManipulationHandler} as both the
- * {@link View.OnKeyListener} and {@link View.OnGenericMotionListener} for the given
- * {@link View}.
- * <p>
- * Handles a {@link Nullable} {@link View} so that it can be used directly with the output of
- * methods such as {@code findViewById}.
- */
- private void registerDirectManipulationHandler(@Nullable View view,
- DirectManipulationHandler handler) {
- if (view == null) {
- return;
- }
- view.setOnKeyListener(handler);
- view.setOnGenericMotionListener(handler);
- }
-
- /**
* A {@link View.OnGenericMotionListener} implementation that delegates handling the
* {@link MotionEvent} to the {@link AccessibilityNodeInfo#ACTION_SCROLL_FORWARD}
* or {@link AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD} depending on the sign of the
@@ -211,8 +200,7 @@ public class RotaryDirectManipulationWidgets extends Fragment {
}
@Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- boolean isActionUp = event.getAction() == KeyEvent.ACTION_UP;
+ public boolean onKey(View view, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -220,10 +208,10 @@ public class RotaryDirectManipulationWidgets extends Fragment {
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (isActionUp) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
int direction = KEYCODE_TO_DIRECTION_MAP.get(keyCode);
- View nextView = v.focusSearch(direction);
- if (areInTheSameTimePicker(v, nextView)) {
+ View nextView = view.focusSearch(direction);
+ if (areInTheSameTimePicker(view, nextView)) {
nextView.requestFocus(direction);
}
}
@@ -239,6 +227,9 @@ public class RotaryDirectManipulationWidgets extends Fragment {
}
TimePicker view1Ancestor = getTimePickerAncestor(view1);
TimePicker view2Ancestor = getTimePickerAncestor(view2);
+ if (view1Ancestor == null || view2Ancestor == null) {
+ return false;
+ }
return view1Ancestor == view2Ancestor;
}
@@ -291,7 +282,6 @@ public class RotaryDirectManipulationWidgets extends Fragment {
if (!(view instanceof TimePicker)) {
return false;
}
- boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -302,7 +292,7 @@ public class RotaryDirectManipulationWidgets extends Fragment {
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (isActionUp) {
+ if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
TimePicker timePicker = (TimePicker) view;
List<NumberPicker> numberPickers = new ArrayList<>();
getNumberPickerDescendants(numberPickers, timePicker);