OSDN Git Service

Added dismiss, expand, and collapse accessibility actions
authorSelim Cinek <cinek@google.com>
Wed, 15 Jun 2016 18:46:37 +0000 (11:46 -0700)
committerSelim Cinek <cinek@google.com>
Wed, 15 Jun 2016 23:30:09 +0000 (16:30 -0700)
Fixes: 20343017
Fixes: 29368014
Change-Id: Ib571242aac04c67aea2f3c3ce76139eaedc1f3f1

core/java/android/view/NotificationHeaderView.java
core/java/com/android/internal/widget/NotificationExpandButton.java [new file with mode: 0644]
core/res/res/layout/notification_template_header.xml
packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java

index 3069e5a..c46acae 100644 (file)
@@ -22,6 +22,7 @@ import android.graphics.Canvas;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ImageView;
@@ -63,6 +64,33 @@ public class NotificationHeaderView extends ViewGroup {
             }
         }
     };
+    final AccessibilityDelegate mExpandDelegate = new AccessibilityDelegate() {
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            if (super.performAccessibilityAction(host, action, args)) {
+                return true;
+            }
+            if (action == AccessibilityNodeInfo.ACTION_COLLAPSE
+                    || action == AccessibilityNodeInfo.ACTION_EXPAND) {
+                mExpandClickListener.onClick(mExpandButton);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            // Avoid that the button description is also spoken
+            info.setClassName(getClass().getName());
+            if (mExpanded) {
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
+            } else {
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
+            }
+        }
+    };
 
     public NotificationHeaderView(Context context) {
         this(context, null);
@@ -92,6 +120,9 @@ public class NotificationHeaderView extends ViewGroup {
         mAppName = findViewById(com.android.internal.R.id.app_name_text);
         mHeaderText = findViewById(com.android.internal.R.id.header_text);
         mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button);
+        if (mExpandButton != null) {
+            mExpandButton.setAccessibilityDelegate(mExpandDelegate);
+        }
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
     }
@@ -230,7 +261,7 @@ public class NotificationHeaderView extends ViewGroup {
     public void setOnClickListener(@Nullable OnClickListener l) {
         mExpandClickListener = l;
         setOnTouchListener(mExpandClickListener != null ? mTouchListener : null);
-        setFocusable(l != null);
+        mExpandButton.setOnClickListener(mExpandClickListener);
         updateTouchListener();
     }
 
@@ -380,19 +411,6 @@ public class NotificationHeaderView extends ViewGroup {
         return this;
     }
 
-    @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        if (mExpandClickListener != null) {
-            AccessibilityNodeInfo.AccessibilityAction expand
-                    = new AccessibilityNodeInfo.AccessibilityAction(
-                    AccessibilityNodeInfo.ACTION_CLICK,
-                    getContext().getString(
-                            com.android.internal.R.string.expand_action_accessibility));
-            info.addAction(expand);
-        }
-    }
-
     public ImageView getExpandButton() {
         return mExpandButton;
     }
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
new file mode 100644 (file)
index 0000000..f4f49b1
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+/**
+ * An expand button in a notification
+ */
+@RemoteViews.RemoteView
+public class NotificationExpandButton extends ImageView {
+    public NotificationExpandButton(Context context) {
+        super(context);
+    }
+
+    public NotificationExpandButton(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public NotificationExpandButton(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+        super.getBoundsOnScreen(outRect, clipToParent);
+        extendRectToMinTouchSize(outRect);
+    }
+
+    private void extendRectToMinTouchSize(Rect rect) {
+        int touchTargetSize = (int) (getResources().getDisplayMetrics().density * 48);
+        rect.left = rect.centerX() - touchTargetSize / 2;
+        rect.right = rect.left + touchTargetSize;
+        rect.top = rect.centerY() - touchTargetSize / 2;
+        rect.bottom = rect.top + touchTargetSize;
+    }
+}
index 38ea92a..38f671c 100644 (file)
@@ -89,7 +89,7 @@
         android:layout="@layout/notification_template_part_chronometer"
         android:visibility="gone"
         />
-    <ImageView
+    <com.android.internal.widget.NotificationExpandButton
         android:id="@+id/expand_button"
         android:background="@null"
         android:layout_width="wrap_content"
index 28a6851..8987081 100644 (file)
@@ -27,6 +27,7 @@ import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -37,11 +38,11 @@ import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Chronometer;
 import android.widget.ImageView;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
@@ -50,6 +51,7 @@ import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackScrollState;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 import com.android.systemui.statusbar.stack.StackViewState;
@@ -149,6 +151,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
                         nowExpanded);
                 logExpansionEvent(true /* userAction */, wasExpanded);
             } else {
+                if (v.isAccessibilityFocused()) {
+                    mPrivateLayout.setFocusOnVisibilityChange();
+                }
                 boolean nowExpanded;
                 if (isPinned()) {
                     nowExpanded = !mExpandedWhenPinned;
@@ -181,6 +186,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
                 }
     };
     private OnClickListener mOnClickListener;
+    private View mChildAfterViewWhenDismissed;
+    private View mGroupParentWhenDismissed;
+    private boolean mRefocusOnDismiss;
 
     public boolean isGroupExpansionChanging() {
         if (isChildInGroup()) {
@@ -717,8 +725,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
         }
     }
 
-    public void setDismissed(boolean dismissed) {
+    public void setDismissed(boolean dismissed, boolean fromAccessibility) {
         mDismissed = dismissed;
+        mGroupParentWhenDismissed = mNotificationParent;
+        mRefocusOnDismiss = fromAccessibility;
+        mChildAfterViewWhenDismissed = null;
+        if (isChildInGroup()) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mNotificationParent.getNotificationChildren();
+            int i = notificationChildren.indexOf(this);
+            if (i != -1 && i < notificationChildren.size() - 1) {
+                mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
+            }
+        }
     }
 
     public boolean isDismissed() {
@@ -750,6 +769,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
         return mChildrenContainer;
     }
 
+    public View getChildAfterViewWhenDismissed() {
+        return mChildAfterViewWhenDismissed;
+    }
+
+    public View getGroupParentWhenDismissed() {
+        return mGroupParentWhenDismissed;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -1326,8 +1353,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
 
     private void updateClearability() {
         // public versions cannot be dismissed
-        mVetoButton.setVisibility(isClearable() && (!mShowingPublic
-                || !mSensitiveHiddenInGeneral) ? View.VISIBLE : View.GONE);
+        mVetoButton.setVisibility(canViewBeDismissed() ? View.VISIBLE : View.GONE);
+    }
+
+    private boolean canViewBeDismissed() {
+        return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
     }
 
     public void makeActionsVisibile() {
@@ -1568,6 +1598,32 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
         }
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        if (canViewBeDismissed()) {
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
+        }
+    }
+
+    @Override
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        if (super.performAccessibilityActionInternal(action, arguments)) {
+            return true;
+        }
+        switch (action) {
+            case AccessibilityNodeInfo.ACTION_DISMISS:
+                NotificationStackScrollLayout.performDismiss(this, mGroupManager,
+                        true /* fromAccessibility */);
+                return true;
+        }
+        return false;
+    }
+
+    public boolean shouldRefocusOnDismiss() {
+        return mRefocusOnDismiss || isAccessibilityFocused();
+    }
+
     public interface OnExpandClickListener {
         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
     }
index a11263a..30ac9ca 100644 (file)
@@ -29,6 +29,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
@@ -117,6 +118,7 @@ public class NotificationContentView extends FrameLayout {
     private PendingIntent mPreviousHeadsUpRemoteInputIntent;
 
     private int mContentHeightAtAnimationStart = UNDEFINED;
+    private boolean mFocusOnVisibilityChange;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
@@ -395,6 +397,19 @@ public class NotificationContentView extends FrameLayout {
         }
     }
 
+    private void focusExpandButtonIfNecessary() {
+        if (mFocusOnVisibilityChange) {
+            NotificationHeaderView header = getVisibleNotificationHeader();
+            if (header != null) {
+                ImageView expandButton = header.getExpandButton();
+                if (expandButton != null) {
+                    expandButton.requestAccessibilityFocus();
+                }
+            }
+            mFocusOnVisibilityChange = false;
+        }
+    }
+
     public void setContentHeight(int contentHeight) {
         mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight());
         selectLayout(mAnimate /* animate */, false /* force */);
@@ -584,7 +599,8 @@ public class NotificationContentView extends FrameLayout {
             updateContentTransformation();
         } else {
             int visibleType = calculateVisibleType();
-            if (visibleType != mVisibleType || force) {
+            boolean changedType = visibleType != mVisibleType;
+            if (changedType || force) {
                 View visibleView = getViewForVisibleType(visibleType);
                 if (visibleView != null) {
                     visibleView.setVisibility(VISIBLE);
@@ -604,6 +620,9 @@ public class NotificationContentView extends FrameLayout {
                     updateViewVisibilities(visibleType);
                 }
                 mVisibleType = visibleType;
+                if (changedType) {
+                    focusExpandButtonIfNecessary();
+                }
                 updateBackgroundColor(animate);
             }
         }
@@ -1133,4 +1152,8 @@ public class NotificationContentView extends FrameLayout {
             mContentHeightAtAnimationStart = UNDEFINED;
         }
     }
+
+    public void setFocusOnVisibilityChange() {
+        mFocusOnVisibilityChange = true;
+    }
 }
index 7c391fb..43f847c 100644 (file)
@@ -769,7 +769,7 @@ public class NotificationStackScrollLayout extends ViewGroup
                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
             }
         }
-        performDismiss(v);
+        performDismiss(v, mGroupManager, false /* fromAccessibility */);
 
         mFalsingManager.onNotificationDismissed();
         if (mFalsingManager.shouldEnforceBouncer()) {
@@ -778,17 +778,18 @@ public class NotificationStackScrollLayout extends ViewGroup
         }
     }
 
-    private void performDismiss(View v) {
+    public static void performDismiss(View v, NotificationGroupManager groupManager,
+            boolean fromAccessibility) {
         if (v instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-            if (mGroupManager.isOnlyChildInSuppressedGroup(row.getStatusBarNotification())) {
+            if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
                 ExpandableNotificationRow groupSummary =
-                        mGroupManager.getLogicalGroupSummary(row.getStatusBarNotification());
+                        groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
                 if (groupSummary.isClearable()) {
-                    performDismiss(groupSummary);
+                    performDismiss(groupSummary, groupManager, fromAccessibility);
                 }
             }
-            row.setDismissed(true);
+            row.setDismissed(true, fromAccessibility);
         }
         final View veto = v.findViewById(R.id.veto);
         if (veto != null && veto.getVisibility() != View.GONE) {
@@ -2265,6 +2266,27 @@ public class NotificationStackScrollLayout extends ViewGroup
 
         // Make sure the clipRect we might have set is removed
         expandableView.setClipTopAmount(0);
+
+        focusNextViewIfFocused(child);
+    }
+
+    private void focusNextViewIfFocused(View view) {
+        if (view instanceof ExpandableNotificationRow) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (row.shouldRefocusOnDismiss()) {
+                View nextView = row.getChildAfterViewWhenDismissed();
+                if (nextView == null) {
+                    View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
+                    nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
+                            ? groupParentWhenDismissed.getTranslationY()
+                            : view.getTranslationY());
+                }
+                if (nextView != null) {
+                    nextView.requestAccessibilityFocus();
+                }
+            }
+        }
+
     }
 
     private boolean isChildInGroup(View child) {