Skip to content

Commit 516240d

Browse files
afohrmandsn5ft
authored andcommitted
[Adaptive][Side Sheet] Added accessibilityPaneTitle to side sheet.
This adds an accessibilityPaneTitle that is spoken by TalkBack on API levels 19 and later. In order to trigger the accessibilityPaneTitle event, it was necessary to add a visibility change when the sheet is expanded and hidden. The sheet now is INVISIBLE at STATE_HIDDEN and VISIBLE at all other states. Also removed the code to switch focus to the sheet on expansion in favor of this approach to align with TalkBack's APIs. PiperOrigin-RevId: 499604691 (cherry picked from commit 3b61327)
1 parent 1200e25 commit 516240d

File tree

3 files changed

+123
-27
lines changed

3 files changed

+123
-27
lines changed

lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import android.view.ViewGroup;
3737
import android.view.ViewGroup.MarginLayoutParams;
3838
import android.view.ViewParent;
39-
import android.view.accessibility.AccessibilityEvent;
4039
import androidx.annotation.IdRes;
4140
import androidx.annotation.NonNull;
4241
import androidx.annotation.Nullable;
@@ -64,6 +63,9 @@
6463
public class SideSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V>
6564
implements Sheet<SideSheetCallback> {
6665

66+
private static final int DEFAULT_ACCESSIBILITY_PANE_TITLE =
67+
R.string.side_sheet_accessibility_pane_title;
68+
6769
private SheetDelegate sheetDelegate;
6870

6971
static final int SIGNIFICANT_VEL_THRESHOLD = 500;
@@ -288,11 +290,14 @@ public boolean onLayoutChild(
288290
} else if (backgroundTint != null) {
289291
ViewCompat.setBackgroundTintList(child, backgroundTint);
290292
}
293+
updateSheetVisibility(child);
294+
291295
updateAccessibilityActions();
292296
if (ViewCompat.getImportantForAccessibility(child)
293297
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
294298
ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
295299
}
300+
ensureAccessibilityPaneTitleIsSet(child);
296301
}
297302
if (viewDragHelper == null) {
298303
viewDragHelper = ViewDragHelper.create(parent, dragCallback);
@@ -320,6 +325,23 @@ public boolean onLayoutChild(
320325
return true;
321326
}
322327

328+
private void updateSheetVisibility(@NonNull View sheet) {
329+
// Sheet visibility is updated on state change to make TalkBack speak the accessibility pane
330+
// title when the sheet expands.
331+
int visibility = state == STATE_HIDDEN ? View.INVISIBLE : View.VISIBLE;
332+
if (sheet.getVisibility() != visibility) {
333+
sheet.setVisibility(visibility);
334+
}
335+
}
336+
337+
private void ensureAccessibilityPaneTitleIsSet(View sheet) {
338+
// Set default accessibility pane title that TalkBack will speak when the sheet is expanded.
339+
if (ViewCompat.getAccessibilityPaneTitle(sheet) == null) {
340+
ViewCompat.setAccessibilityPaneTitle(
341+
sheet, sheet.getResources().getString(DEFAULT_ACCESSIBILITY_PANE_TITLE));
342+
}
343+
}
344+
323345
private void maybeAssignCoplanarSiblingViewBasedId(@NonNull CoordinatorLayout parent) {
324346
if (coplanarSiblingViewRef == null && coplanarSiblingViewId != View.NO_ID) {
325347
View coplanarSiblingView = parent.findViewById(coplanarSiblingViewId);
@@ -360,7 +382,7 @@ private int calculateCurrentOffset(int savedOutwardEdge, V child) {
360382
@Override
361383
public boolean onInterceptTouchEvent(
362384
@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) {
363-
if (!child.isShown() || !draggable) {
385+
if (!shouldInterceptTouchEvent(child)) {
364386
ignoreEvents = true;
365387
return false;
366388
}
@@ -392,6 +414,10 @@ public boolean onInterceptTouchEvent(
392414
&& viewDragHelper.shouldInterceptTouchEvent(event);
393415
}
394416

417+
private boolean shouldInterceptTouchEvent(@NonNull V child) {
418+
return (child.isShown() || ViewCompat.getAccessibilityPaneTitle(child) != null) && draggable;
419+
}
420+
395421
int getSignificantVelocityThreshold() {
396422
return SIGNIFICANT_VEL_THRESHOLD;
397423
}
@@ -578,9 +604,7 @@ void setStateInternal(@SheetState int state) {
578604
return;
579605
}
580606

581-
if (state == STATE_EXPANDED) {
582-
updateAccessibilityFocusOnExpansion();
583-
}
607+
updateSheetVisibility(sheet);
584608

585609
for (SheetCallback callback : callbacks) {
586610
callback.onStateChanged(sheet, state);
@@ -899,22 +923,6 @@ public static <V extends View> SideSheetBehavior<V> from(@NonNull V view) {
899923
return (SideSheetBehavior<V>) behavior;
900924
}
901925

902-
private void updateAccessibilityFocusOnExpansion() {
903-
if (viewRef == null) {
904-
return;
905-
}
906-
View view = viewRef.get();
907-
if (view instanceof ViewGroup && ((ViewGroup) view).getChildCount() > 0) {
908-
ViewGroup viewContainer = (ViewGroup) view;
909-
View firstNestedChild = viewContainer.getChildAt(0);
910-
if (firstNestedChild != null) {
911-
firstNestedChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
912-
}
913-
} else {
914-
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
915-
}
916-
}
917-
918926
private void updateAccessibilityActions() {
919927
if (viewRef == null) {
920928
return;

lib/java/com/google/android/material/sidesheet/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
~ limitations under the License.
1616
-->
1717
<resources>
18+
<!-- Side sheet accessibility pane title -->
19+
<string name="side_sheet_accessibility_pane_title"
20+
description="Title of the side sheet's accessibility pane that is spoken by TalkBack when the side sheet appears on screen. [CHAR_LIMIT=NONE]">Side Sheet</string>
1821
<!-- The class name for the SideSheetBehavior -->
1922
<string name="side_sheet_behavior" translatable="false">com.google.android.material.sidesheet.SideSheetBehavior</string>
2023
</resources>

lib/javatests/com/google/android/material/sidesheet/SideSheetBehaviorTest.java

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,23 @@
1717

1818
import com.google.android.material.test.R;
1919

20+
import static com.google.android.material.sidesheet.Sheet.STATE_DRAGGING;
2021
import static com.google.android.material.sidesheet.Sheet.STATE_EXPANDED;
2122
import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN;
23+
import static com.google.android.material.sidesheet.Sheet.STATE_SETTLING;
2224
import static com.google.common.truth.Truth.assertThat;
2325
import static org.robolectric.Shadows.shadowOf;
2426

27+
import android.annotation.TargetApi;
28+
import android.os.Build.VERSION;
29+
import android.os.Build.VERSION_CODES;
2530
import android.os.Bundle;
2631
import android.os.Looper;
2732
import androidx.appcompat.app.AppCompatActivity;
2833
import android.view.View;
2934
import androidx.annotation.NonNull;
35+
import androidx.coordinatorlayout.widget.CoordinatorLayout;
36+
import androidx.core.view.ViewCompat;
3037
import org.junit.Before;
3138
import org.junit.Test;
3239
import org.junit.runner.RunWith;
@@ -39,30 +46,36 @@ public class SideSheetBehaviorTest {
3946

4047
@NonNull TestActivity activity;
4148

49+
private View sideSheet;
50+
private SideSheetBehavior<View> sideSheetBehavior;
51+
4252
@Before
4353
public void createActivity() {
4454
activity = Robolectric.buildActivity(TestActivity.class).setup().get();
55+
CoordinatorLayout coordinatorLayout =
56+
(CoordinatorLayout) activity.getLayoutInflater().inflate(R.layout.test_side_sheet, null);
57+
sideSheet = coordinatorLayout.findViewById(R.id.test_side_sheet_container);
58+
sideSheetBehavior = SideSheetBehavior.from(sideSheet);
59+
activity.setContentView(coordinatorLayout);
60+
61+
// Wait until the layout is measured.
62+
shadowOf(Looper.getMainLooper()).idle();
4563
}
4664

4765
@Test
4866
public void onInitialization_sheetIsHidden() {
49-
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
50-
5167
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);
5268
}
5369

5470
@Test
5571
public void expand_ofInitializedSheet_yieldsExpandedState() {
56-
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
57-
5872
expandSheet(sideSheetBehavior);
5973

6074
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_EXPANDED);
6175
}
6276

6377
@Test
6478
public void expand_ofExpandedSheet_isIdempotent() {
65-
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
6679
expandSheet(sideSheetBehavior);
6780
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_EXPANDED);
6881

@@ -84,14 +97,86 @@ public void hide_ofExpandedSheet_yieldsHiddenState() {
8497

8598
@Test
8699
public void hide_ofHiddenSheet_isIdempotent() {
87-
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
88100
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);
89101

90102
hideSheet(sideSheetBehavior);
91103

92104
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);
93105
}
94106

107+
@Test
108+
public void onInitialization_sheetIsInvisible() {
109+
assertThat(sideSheet.getVisibility()).isEqualTo(View.INVISIBLE);
110+
}
111+
112+
@Test
113+
public void show_ofHiddenSheet_sheetIsVisible() {
114+
expandSheet(sideSheetBehavior);
115+
116+
assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
117+
}
118+
119+
@Test
120+
public void hide_ofExpandedSheet_sheetIsInvisible() {
121+
expandSheet(sideSheetBehavior);
122+
hideSheet(sideSheetBehavior);
123+
124+
assertThat(sideSheet.getVisibility()).isEqualTo(View.INVISIBLE);
125+
}
126+
127+
@Test
128+
public void drag_ofExpandedSheet_sheetIsVisible() {
129+
expandSheet(sideSheetBehavior);
130+
131+
sideSheetBehavior.setStateInternal(STATE_DRAGGING);
132+
shadowOf(Looper.getMainLooper()).idle();
133+
134+
assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
135+
}
136+
137+
@Test
138+
public void settle_ofHiddenSheet_sheetIsVisible() {
139+
// Sheet is hidden on initialization.
140+
sideSheetBehavior.setStateInternal(STATE_SETTLING);
141+
shadowOf(Looper.getMainLooper()).idle();
142+
143+
assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
144+
}
145+
146+
@Test
147+
public void settle_ofExpandedSheet_sheetIsVisible() {
148+
expandSheet(sideSheetBehavior);
149+
150+
sideSheetBehavior.setStateInternal(STATE_SETTLING);
151+
shadowOf(Looper.getMainLooper()).idle();
152+
153+
assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
154+
}
155+
156+
@TargetApi(VERSION_CODES.KITKAT)
157+
@Test
158+
public void setAccessibilityPaneTitle_ofDefaultSheet_customTitleIsUsed() {
159+
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
160+
return;
161+
}
162+
String defaultAccessibilityPaneTitle =
163+
String.valueOf(ViewCompat.getAccessibilityPaneTitle(sideSheet));
164+
shadowOf(Looper.getMainLooper()).idle();
165+
166+
String customAccessibilityPaneTitle = "Custom side sheet accessibility pane title";
167+
168+
ViewCompat.setAccessibilityPaneTitle(sideSheet, customAccessibilityPaneTitle);
169+
shadowOf(Looper.getMainLooper()).idle();
170+
171+
String updatedAccessibilityPaneTitle =
172+
String.valueOf(ViewCompat.getAccessibilityPaneTitle(sideSheet));
173+
shadowOf(Looper.getMainLooper()).idle();
174+
175+
assertThat(defaultAccessibilityPaneTitle).isNotNull();
176+
assertThat(updatedAccessibilityPaneTitle).isEqualTo(customAccessibilityPaneTitle);
177+
assertThat(updatedAccessibilityPaneTitle).isNotEqualTo(defaultAccessibilityPaneTitle);
178+
}
179+
95180
private void expandSheet(SideSheetBehavior<View> sideSheetBehavior) {
96181
sideSheetBehavior.expand();
97182
shadowOf(Looper.getMainLooper()).idle();

0 commit comments

Comments
 (0)