From 152fc131c53fbf62f84612f5aafc6aace58f57ad Mon Sep 17 00:00:00 2001 From: Julia Tuttle Date: Tue, 4 Oct 2022 16:54:07 -0400 Subject: [PATCH] Mark actions unimportant for accessibility when obscured by RemoteInputView. To prevent animation jank, we don't actually hide or remove the action buttons (like "reply") on a notification while they're obscured by the remote input view (like while the user is typing their reply). Visually, this is fine, but Voice Access still sees those buttons as important. If the user speaks the action (like "reply"), Voice Access will activate the buttons, but nothing visible will happen. If the user has numbers shown, Voice Access will number the buttons, but the numbers will appear to point at the remote input view instead. To avoid this, we mark the NotificationActionListLayout as unimportant for accessibility while the remote input view is visible, and return it to "auto" important when the remote input view isn't visible. Bug: 230297786 Test: atest NotificationContentViewTest Change-Id: I50fdda3adabd0039ed84b3781c0d275fc176ef89 --- .../row/NotificationContentView.java | 19 +++++++ .../row/NotificationContentViewTest.java | 57 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index df81c0ed3a61e..8de036542c8f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1986,6 +1986,25 @@ public class NotificationContentView extends FrameLayout implements Notification public void setRemoteInputVisible(boolean remoteInputVisible) { mRemoteInputVisible = remoteInputVisible; setClipChildren(!remoteInputVisible); + setActionsImportanceForAccessibility( + remoteInputVisible ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } + + private void setActionsImportanceForAccessibility(int mode) { + if (mExpandedChild != null) { + setActionsImportanceForAccessibility(mode, mExpandedChild); + } + if (mHeadsUpChild != null) { + setActionsImportanceForAccessibility(mode, mHeadsUpChild); + } + } + + private void setActionsImportanceForAccessibility(int mode, View child) { + View actionsCandidate = child.findViewById(com.android.internal.R.id.actions); + if (actionsCandidate != null) { + actionsCandidate.setImportantForAccessibility(mode); + } } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 682ff1fc8c523..81b8e98029cee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; +import com.android.internal.widget.NotificationActionListLayout; import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -142,4 +143,60 @@ public class NotificationContentViewTest extends SysuiTestCase { verify(mockExpandedEB, times(1)).requestAccessibilityFocus(); verify(mockHeadsUpEB, times(0)).requestAccessibilityFocus(); } + + @Test + @UiThreadTest + public void testRemoteInputVisibleSetsActionsUnimportantHideDescendantsForAccessibility() { + View mockContracted = mock(NotificationHeaderView.class); + + View mockExpandedActions = mock(NotificationActionListLayout.class); + View mockExpanded = mock(NotificationHeaderView.class); + when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn( + mockExpandedActions); + + View mockHeadsUpActions = mock(NotificationActionListLayout.class); + View mockHeadsUp = mock(NotificationHeaderView.class); + when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn( + mockHeadsUpActions); + + mView.setContractedChild(mockContracted); + mView.setExpandedChild(mockExpanded); + mView.setHeadsUpChild(mockHeadsUp); + + mView.setRemoteInputVisible(true); + + verify(mockContracted, times(0)).findViewById(0); + verify(mockExpandedActions, times(1)).setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + verify(mockHeadsUpActions, times(1)).setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } + + @Test + @UiThreadTest + public void testRemoteInputInvisibleSetsActionsAutoImportantForAccessibility() { + View mockContracted = mock(NotificationHeaderView.class); + + View mockExpandedActions = mock(NotificationActionListLayout.class); + View mockExpanded = mock(NotificationHeaderView.class); + when(mockExpanded.findViewById(com.android.internal.R.id.actions)).thenReturn( + mockExpandedActions); + + View mockHeadsUpActions = mock(NotificationActionListLayout.class); + View mockHeadsUp = mock(NotificationHeaderView.class); + when(mockHeadsUp.findViewById(com.android.internal.R.id.actions)).thenReturn( + mockHeadsUpActions); + + mView.setContractedChild(mockContracted); + mView.setExpandedChild(mockExpanded); + mView.setHeadsUpChild(mockHeadsUp); + + mView.setRemoteInputVisible(false); + + verify(mockContracted, times(0)).findViewById(0); + verify(mockExpandedActions, times(1)).setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + verify(mockHeadsUpActions, times(1)).setImportantForAccessibility( + View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + } }