Add customs accessibility actions to snooze, allow it to be focused
- Add plumbing for accessibility action IDs, enabling custom
accessibility options in the local context menu
- Allow snooze view to be focusable
- When option is selected announces the selection via window state change
Test: manual
Bug: 34840333
Change-Id: Idca0b0e00e792d3e3f71fc6a15b9b26d9136f6da
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 3cd5d89..f6cf035 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -22,6 +22,7 @@
import android.service.notification.StatusBarNotification;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@ProvidesInterface(version = NotificationSwipeActionHelper.VERSION)
@DependsOn(target = SnoozeOption.class)
@@ -56,19 +57,17 @@
public boolean swipedFastEnough(float translation, float velocity);
@ProvidesInterface(version = SnoozeOption.VERSION)
- public static class SnoozeOption {
- public static final int VERSION = 1;
- public int snoozeForMinutes;
- public SnoozeCriterion criterion;
- public CharSequence description;
- public CharSequence confirmation;
+ public interface SnoozeOption {
+ public static final int VERSION = 2;
- public SnoozeOption(SnoozeCriterion crit, int minsToSnoozeFor, CharSequence desc,
- CharSequence confirm) {
- criterion = crit;
- snoozeForMinutes = minsToSnoozeFor;
- description = desc;
- confirmation = confirm;
- }
+ public SnoozeCriterion getSnoozeCriterion();
+
+ public CharSequence getDescription();
+
+ public CharSequence getConfirmation();
+
+ public int getMinutesToSnoozeFor();
+
+ public AccessibilityAction getAccessibilityAction();
}
}
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index b70f24b..3209f27 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -20,12 +20,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:clickable="true"
android:background="@color/notification_guts_bg_color"
android:theme="@*android:style/Theme.DeviceDefault.Light">
<RelativeLayout
- android:layout_width="match_parent"
android:id="@+id/notification_snooze"
+ android:layout_width="match_parent"
android:layout_height="@dimen/snooze_snackbar_min_height">
<TextView
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 3c6ad23..b27dedd 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -78,5 +78,13 @@
<item type="id" name="action_move_tl_50" />
<item type="id" name="action_move_tl_30" />
<item type="id" name="action_move_rb_full" />
+
+ <!-- Accessibility actions for the notification menu -->
+ <item type="id" name="action_snooze_undo"/>
+ <item type="id" name="action_snooze_15_min"/>
+ <item type="id" name="action_snooze_30_min"/>
+ <item type="id" name="action_snooze_1_hour"/>
+ <item type="id" name="action_snooze_2_hours"/>
+ <item type="id" name="action_snooze_assistant_suggestion_1"/>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index 0e5e416..c45ca54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -21,11 +21,14 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
+import android.os.Bundle;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.SpannableString;
@@ -35,6 +38,9 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -45,6 +51,10 @@
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
+ /**
+ * If this changes more number increases, more assistant action resId's should be defined for
+ * accessibility purposes, see {@link #setSnoozeOptions(List)}
+ */
private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
private NotificationGuts mGutsContainer;
private NotificationSwipeActionHelper mSnoozeListener;
@@ -79,16 +89,60 @@
mDivider = findViewById(R.id.divider);
mDivider.setAlpha(0f);
mSnoozeOptionContainer = (ViewGroup) findViewById(R.id.snooze_options);
+ mSnoozeOptionContainer.setVisibility(View.INVISIBLE);
mSnoozeOptionContainer.setAlpha(0f);
// Create the different options based on list
mSnoozeOptions = getDefaultSnoozeOptions();
createOptionViews();
- // Default to first option in list
setSelected(mDefaultOption);
}
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null && mGutsContainer.isExposed()) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ event.getText().add(mSelectedOptionText.getText());
+ }
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.addAction(new AccessibilityAction(R.id.action_snooze_undo,
+ getResources().getString(R.string.snooze_undo)));
+ int count = mSnoozeOptions.size();
+ for (int i = 0; i < count; i++) {
+ AccessibilityAction action = mSnoozeOptions.get(i).getAccessibilityAction();
+ if (action != null) {
+ info.addAction(action);
+ }
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
+ return true;
+ }
+ if (action == R.id.action_snooze_undo) {
+ undoSnooze(mUndoButton);
+ return true;
+ }
+ for (int i = 0; i < mSnoozeOptions.size(); i++) {
+ SnoozeOption so = mSnoozeOptions.get(i);
+ if (so.getAccessibilityAction() != null
+ && so.getAccessibilityAction().getId() == action) {
+ setSelected(so);
+ return true;
+ }
+ }
+ return false;
+ }
+
public void setSnoozeOptions(final List<SnoozeCriterion> snoozeList) {
if (snoozeList == null) {
return;
@@ -98,7 +152,10 @@
final int count = Math.min(MAX_ASSISTANT_SUGGESTIONS, snoozeList.size());
for (int i = 0; i < count; i++) {
SnoozeCriterion sc = snoozeList.get(i);
- mSnoozeOptions.add(new SnoozeOption(sc, 0, sc.getExplanation(), sc.getConfirmation()));
+ AccessibilityAction action = new AccessibilityAction(
+ R.id.action_snooze_assistant_suggestion_1, sc.getExplanation());
+ mSnoozeOptions.add(new NotificationSnoozeOption(sc, 0, sc.getExplanation(),
+ sc.getConfirmation(), action));
}
createOptionViews();
}
@@ -117,15 +174,16 @@
private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
ArrayList<SnoozeOption> options = new ArrayList<>();
- options.add(createOption(15 /* minutes */));
- options.add(createOption(30 /* minutes */));
- mDefaultOption = createOption(60 /* minutes */);
+
+ options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
+ options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
+ mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
options.add(mDefaultOption);
- options.add(createOption(60 * 2 /* minutes */));
+ options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
return options;
}
- private SnoozeOption createOption(int minutes) {
+ private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
int pluralResId = showInHours
@@ -137,7 +195,9 @@
SpannableString string = new SpannableString(resultText);
string.setSpan(new StyleSpan(Typeface.BOLD),
resultText.length() - description.length(), resultText.length(), 0 /* flags */);
- return new SnoozeOption(null, minutes, description, string);
+ AccessibilityAction action = new AccessibilityAction(accessibilityActionId, description);
+ return new NotificationSnoozeOption(null, minutes, description, string,
+ action);
}
private void createOptionViews() {
@@ -149,7 +209,7 @@
TextView tv = (TextView) inflater.inflate(R.layout.notification_snooze_option,
mSnoozeOptionContainer, false);
mSnoozeOptionContainer.addView(tv);
- tv.setText(option.description);
+ tv.setText(option.getDescription());
tv.setTag(option);
tv.setOnClickListener(this);
}
@@ -184,18 +244,36 @@
mDivider.getAlpha(), show ? 1f : 0f);
ObjectAnimator optionAnim = ObjectAnimator.ofFloat(mSnoozeOptionContainer, View.ALPHA,
mSnoozeOptionContainer.getAlpha(), show ? 1f : 0f);
+ mSnoozeOptionContainer.setVisibility(View.VISIBLE);
mExpandAnimation = new AnimatorSet();
mExpandAnimation.playTogether(dividerAnim, optionAnim);
mExpandAnimation.setDuration(150);
mExpandAnimation.setInterpolator(show ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
+ mExpandAnimation.addListener(new AnimatorListenerAdapter() {
+ boolean cancelled = false;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!show && !cancelled) {
+ mSnoozeOptionContainer.setVisibility(View.INVISIBLE);
+ mSnoozeOptionContainer.setAlpha(0f);
+ }
+ }
+ });
mExpandAnimation.start();
}
private void setSelected(SnoozeOption option) {
mSelectedOption = option;
- mSelectedOptionText.setText(option.confirmation);
+ mSelectedOptionText.setText(option.getConfirmation());
showSnoozeOptions(false);
hideSelectedOption();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
@Override
@@ -212,20 +290,24 @@
showSnoozeOptions(!mExpanded);
} else {
// Undo snooze was selected
- mSelectedOption = null;
- int[] parentLoc = new int[2];
- int[] targetLoc = new int[2];
- mGutsContainer.getLocationOnScreen(parentLoc);
- v.getLocationOnScreen(targetLoc);
- final int centerX = v.getWidth() / 2;
- final int centerY = v.getHeight() / 2;
- final int x = targetLoc[0] - parentLoc[0] + centerX;
- final int y = targetLoc[1] - parentLoc[1] + centerY;
- showSnoozeOptions(false);
- mGutsContainer.closeControls(x, y, false /* save */, false /* force */);
+ undoSnooze(v);
}
}
+ private void undoSnooze(View v) {
+ mSelectedOption = null;
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ showSnoozeOptions(false);
+ mGutsContainer.closeControls(x, y, false /* save */, false /* force */);
+ }
+
@Override
public int getActualHeight() {
return mExpanded ? getHeight() : mCollapsedHeight;
@@ -270,4 +352,48 @@
public boolean isLeavebehind() {
return true;
}
+
+ public class NotificationSnoozeOption implements SnoozeOption {
+ private SnoozeCriterion mCriterion;
+ private int mMinutesToSnoozeFor;
+ private CharSequence mDescription;
+ private CharSequence mConfirmation;
+ private AccessibilityAction mAction;
+
+ public NotificationSnoozeOption(SnoozeCriterion sc, int minToSnoozeFor,
+ CharSequence description,
+ CharSequence confirmation, AccessibilityAction action) {
+ mCriterion = sc;
+ mMinutesToSnoozeFor = minToSnoozeFor;
+ mDescription = description;
+ mConfirmation = confirmation;
+ mAction = action;
+ }
+
+ @Override
+ public SnoozeCriterion getSnoozeCriterion() {
+ return mCriterion;
+ }
+
+ @Override
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public CharSequence getConfirmation() {
+ return mConfirmation;
+ }
+
+ @Override
+ public int getMinutesToSnoozeFor() {
+ return mMinutesToSnoozeFor;
+ }
+
+ @Override
+ public AccessibilityAction getAccessibilityAction() {
+ return mAction;
+ }
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ed0ca30..17631c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -5985,11 +5985,12 @@
}
public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
- if (snoozeOption.criterion != null) {
- mNotificationListener.snoozeNotification(sbn.getKey(), snoozeOption.criterion.getId());
+ if (snoozeOption.getSnoozeCriterion() != null) {
+ mNotificationListener.snoozeNotification(sbn.getKey(),
+ snoozeOption.getSnoozeCriterion().getId());
} else {
mNotificationListener.snoozeNotification(sbn.getKey(),
- snoozeOption.snoozeForMinutes * 60 * 1000);
+ snoozeOption.getMinutesToSnoozeFor() * 60 * 1000);
}
}