Skip to content

Commit

Permalink
[Adaptive][Side Sheet] Added accessibilityPaneTitle to side sheet.
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
afohrman authored and dsn5ft committed Jan 5, 2023
1 parent 1200e25 commit 516240d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -64,6 +63,9 @@
public class SideSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V>
implements Sheet<SideSheetCallback> {

private static final int DEFAULT_ACCESSIBILITY_PANE_TITLE =
R.string.side_sheet_accessibility_pane_title;

private SheetDelegate sheetDelegate;

static final int SIGNIFICANT_VEL_THRESHOLD = 500;
Expand Down Expand Up @@ -288,11 +290,14 @@ public boolean onLayoutChild(
} else if (backgroundTint != null) {
ViewCompat.setBackgroundTintList(child, backgroundTint);
}
updateSheetVisibility(child);

updateAccessibilityActions();
if (ViewCompat.getImportantForAccessibility(child)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
ensureAccessibilityPaneTitleIsSet(child);
}
if (viewDragHelper == null) {
viewDragHelper = ViewDragHelper.create(parent, dragCallback);
Expand Down Expand Up @@ -320,6 +325,23 @@ public boolean onLayoutChild(
return true;
}

private void updateSheetVisibility(@NonNull View sheet) {
// Sheet visibility is updated on state change to make TalkBack speak the accessibility pane
// title when the sheet expands.
int visibility = state == STATE_HIDDEN ? View.INVISIBLE : View.VISIBLE;
if (sheet.getVisibility() != visibility) {
sheet.setVisibility(visibility);
}
}

private void ensureAccessibilityPaneTitleIsSet(View sheet) {
// Set default accessibility pane title that TalkBack will speak when the sheet is expanded.
if (ViewCompat.getAccessibilityPaneTitle(sheet) == null) {
ViewCompat.setAccessibilityPaneTitle(
sheet, sheet.getResources().getString(DEFAULT_ACCESSIBILITY_PANE_TITLE));
}
}

private void maybeAssignCoplanarSiblingViewBasedId(@NonNull CoordinatorLayout parent) {
if (coplanarSiblingViewRef == null && coplanarSiblingViewId != View.NO_ID) {
View coplanarSiblingView = parent.findViewById(coplanarSiblingViewId);
Expand Down Expand Up @@ -360,7 +382,7 @@ private int calculateCurrentOffset(int savedOutwardEdge, V child) {
@Override
public boolean onInterceptTouchEvent(
@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) {
if (!child.isShown() || !draggable) {
if (!shouldInterceptTouchEvent(child)) {
ignoreEvents = true;
return false;
}
Expand Down Expand Up @@ -392,6 +414,10 @@ public boolean onInterceptTouchEvent(
&& viewDragHelper.shouldInterceptTouchEvent(event);
}

private boolean shouldInterceptTouchEvent(@NonNull V child) {
return (child.isShown() || ViewCompat.getAccessibilityPaneTitle(child) != null) && draggable;
}

int getSignificantVelocityThreshold() {
return SIGNIFICANT_VEL_THRESHOLD;
}
Expand Down Expand Up @@ -578,9 +604,7 @@ void setStateInternal(@SheetState int state) {
return;
}

if (state == STATE_EXPANDED) {
updateAccessibilityFocusOnExpansion();
}
updateSheetVisibility(sheet);

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

private void updateAccessibilityFocusOnExpansion() {
if (viewRef == null) {
return;
}
View view = viewRef.get();
if (view instanceof ViewGroup && ((ViewGroup) view).getChildCount() > 0) {
ViewGroup viewContainer = (ViewGroup) view;
View firstNestedChild = viewContainer.getChildAt(0);
if (firstNestedChild != null) {
firstNestedChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
} else {
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
}

private void updateAccessibilityActions() {
if (viewRef == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
~ limitations under the License.
-->
<resources>
<!-- Side sheet accessibility pane title -->
<string name="side_sheet_accessibility_pane_title"
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>
<!-- The class name for the SideSheetBehavior -->
<string name="side_sheet_behavior" translatable="false">com.google.android.material.sidesheet.SideSheetBehavior</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@

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

import static com.google.android.material.sidesheet.Sheet.STATE_DRAGGING;
import static com.google.android.material.sidesheet.Sheet.STATE_EXPANDED;
import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN;
import static com.google.android.material.sidesheet.Sheet.STATE_SETTLING;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;

import android.annotation.TargetApi;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Looper;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -39,30 +46,36 @@ public class SideSheetBehaviorTest {

@NonNull TestActivity activity;

private View sideSheet;
private SideSheetBehavior<View> sideSheetBehavior;

@Before
public void createActivity() {
activity = Robolectric.buildActivity(TestActivity.class).setup().get();
CoordinatorLayout coordinatorLayout =
(CoordinatorLayout) activity.getLayoutInflater().inflate(R.layout.test_side_sheet, null);
sideSheet = coordinatorLayout.findViewById(R.id.test_side_sheet_container);
sideSheetBehavior = SideSheetBehavior.from(sideSheet);
activity.setContentView(coordinatorLayout);

// Wait until the layout is measured.
shadowOf(Looper.getMainLooper()).idle();
}

@Test
public void onInitialization_sheetIsHidden() {
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();

assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);
}

@Test
public void expand_ofInitializedSheet_yieldsExpandedState() {
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();

expandSheet(sideSheetBehavior);

assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_EXPANDED);
}

@Test
public void expand_ofExpandedSheet_isIdempotent() {
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
expandSheet(sideSheetBehavior);
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_EXPANDED);

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

@Test
public void hide_ofHiddenSheet_isIdempotent() {
SideSheetBehavior<View> sideSheetBehavior = new SideSheetBehavior<>();
assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);

hideSheet(sideSheetBehavior);

assertThat(sideSheetBehavior.getState()).isEqualTo(STATE_HIDDEN);
}

@Test
public void onInitialization_sheetIsInvisible() {
assertThat(sideSheet.getVisibility()).isEqualTo(View.INVISIBLE);
}

@Test
public void show_ofHiddenSheet_sheetIsVisible() {
expandSheet(sideSheetBehavior);

assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
}

@Test
public void hide_ofExpandedSheet_sheetIsInvisible() {
expandSheet(sideSheetBehavior);
hideSheet(sideSheetBehavior);

assertThat(sideSheet.getVisibility()).isEqualTo(View.INVISIBLE);
}

@Test
public void drag_ofExpandedSheet_sheetIsVisible() {
expandSheet(sideSheetBehavior);

sideSheetBehavior.setStateInternal(STATE_DRAGGING);
shadowOf(Looper.getMainLooper()).idle();

assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
}

@Test
public void settle_ofHiddenSheet_sheetIsVisible() {
// Sheet is hidden on initialization.
sideSheetBehavior.setStateInternal(STATE_SETTLING);
shadowOf(Looper.getMainLooper()).idle();

assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
}

@Test
public void settle_ofExpandedSheet_sheetIsVisible() {
expandSheet(sideSheetBehavior);

sideSheetBehavior.setStateInternal(STATE_SETTLING);
shadowOf(Looper.getMainLooper()).idle();

assertThat(sideSheet.getVisibility()).isEqualTo(View.VISIBLE);
}

@TargetApi(VERSION_CODES.KITKAT)
@Test
public void setAccessibilityPaneTitle_ofDefaultSheet_customTitleIsUsed() {
if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
return;
}
String defaultAccessibilityPaneTitle =
String.valueOf(ViewCompat.getAccessibilityPaneTitle(sideSheet));
shadowOf(Looper.getMainLooper()).idle();

String customAccessibilityPaneTitle = "Custom side sheet accessibility pane title";

ViewCompat.setAccessibilityPaneTitle(sideSheet, customAccessibilityPaneTitle);
shadowOf(Looper.getMainLooper()).idle();

String updatedAccessibilityPaneTitle =
String.valueOf(ViewCompat.getAccessibilityPaneTitle(sideSheet));
shadowOf(Looper.getMainLooper()).idle();

assertThat(defaultAccessibilityPaneTitle).isNotNull();
assertThat(updatedAccessibilityPaneTitle).isEqualTo(customAccessibilityPaneTitle);
assertThat(updatedAccessibilityPaneTitle).isNotEqualTo(defaultAccessibilityPaneTitle);
}

private void expandSheet(SideSheetBehavior<View> sideSheetBehavior) {
sideSheetBehavior.expand();
shadowOf(Looper.getMainLooper()).idle();
Expand Down

0 comments on commit 516240d

Please sign in to comment.