From c0149f4e7046fe6ab796d4aa79e42b0eebb044ff Mon Sep 17 00:00:00 2001 From: Saumya Prakash Date: Fri, 10 Jan 2025 23:12:23 +0000 Subject: [PATCH] Fix talkback not automatically announcing in Gesture Nav Tutorial This change explicitly sets the Talkback attributes to allow for automatically scrolling to the subtitle in the gesture navigation tutorial. Previously, you would have to tap the subtitle for it to be announced. Fix: 386884587 Test: Run the tutorial with talkback enabled and observe talkback announcing the title and subtitle of the current gesture Flag: EXEMPT bugfix Change-Id: I6ca2c1654f9e481165e2135e9afd72fa178f8184 --- .../redesigned_gesture_tutorial_fragment.xml | 7 +++ .../interaction/TutorialController.java | 45 +++++++++++++++---- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml index b004dfda6a..7530c286f4 100644 --- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml @@ -147,6 +147,7 @@ android:layout_height="wrap_content" android:layout_marginTop="104dp" android:accessibilityHeading="true" + android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_feedback_subtitle" android:gravity="top" android:lineSpacingExtra="-1sp" android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle" @@ -161,6 +162,8 @@ android:layout_marginTop="24dp" android:lineSpacingExtra="4sp" android:textAppearance="@style/TextAppearance.GestureTutorial.MainSubtitle" + android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_title" + android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_action_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" /> @@ -224,6 +227,10 @@ android:layout_marginBottom="@dimen/gesture_tutorial_done_button_bottom_margin" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" + android:clickable="true" + android:focusableInTouchMode="true" + android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_subtitle" + android:contentDescription="@string/gesture_tutorial_action_button_label" android:background="@drawable/gesture_tutorial_action_button_background" android:stateListAnimator="@null" android:text="@string/gesture_tutorial_action_button_label" diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java index 7d14a3ebd5..0fc95e2102 100644 --- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java +++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java @@ -80,8 +80,8 @@ abstract class TutorialController implements BackGestureAttemptCallback, private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips"; private static final int FEEDBACK_ANIMATION_MS = 133; - private static final int RIPPLE_VISIBLE_MS = 300; - private static final int GESTURE_ANIMATION_DELAY_MS = 1500; + private static final int SUBTITLE_ANNOUNCE_DELAY_MS = 3000; + private static final int DONE_BUTTON_ANNOUNCE_DELAY_MS = 4000; private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 3000; private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000; protected float mExitingAppEndingCornerRadius; @@ -124,10 +124,12 @@ abstract class TutorialController implements BackGestureAttemptCallback, // These runnables should be used when posting callbacks to their views and cleared from their // views before posting new callbacks. private final Runnable mTitleViewCallback; + private final Runnable mSubtitleViewCallback; @Nullable private Runnable mFeedbackViewCallback; @Nullable private Runnable mFakeTaskViewCallback; @Nullable private Runnable mFakeTaskbarViewCallback; private final Runnable mShowFeedbackRunnable; + private final AccessibilityManager mAccessibilityManager; TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { mTutorialFragment = tutorialFragment; @@ -175,6 +177,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFeedbackTitleView.setText(getIntroductionTitle()); mFeedbackSubtitleView.setText(getIntroductionSubtitle()); + mExitingAppView.setClipToOutline(true); mExitingAppView.setOutlineProvider(new ViewOutlineProvider() { @Override @@ -183,8 +186,16 @@ abstract class TutorialController implements BackGestureAttemptCallback, } }); - mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_FOCUSED); + mAccessibilityManager = AccessibilityManager.getInstance(mContext); + mTitleViewCallback = () -> { + mFeedbackTitleView.requestFocus(); + mFeedbackTitleView.sendAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_FOCUSED); + }; + mSubtitleViewCallback = () -> { + mFeedbackSubtitleView.requestFocus(); + mFeedbackSubtitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + }; mShowFeedbackRunnable = () -> { mFeedbackView.setAlpha(0f); mFeedbackView.setScaleX(0.95f); @@ -203,10 +214,10 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFeedbackViewCallback = mTutorialFragment::continueTutorial; mFeedbackView.postDelayed( mFeedbackViewCallback, - AccessibilityManager.getInstance(mContext) - .getRecommendedTimeoutMillis( - ADVANCE_TUTORIAL_TIMEOUT_MS, - AccessibilityManager.FLAG_CONTENT_TEXT)); + mAccessibilityManager.getRecommendedTimeoutMillis( + ADVANCE_TUTORIAL_TIMEOUT_MS, + AccessibilityManager.FLAG_CONTENT_TEXT + | AccessibilityManager.FLAG_CONTENT_CONTROLS)); } }) .start(); @@ -404,6 +415,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, int subtitleResId, boolean isGestureSuccessful) { mFeedbackTitleView.removeCallbacks(mTitleViewCallback); + mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback); if (mFeedbackViewCallback != null) { mFeedbackView.removeCallbacks(mFeedbackViewCallback); mFeedbackViewCallback = null; @@ -411,6 +423,15 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFeedbackTitleView.setText(titleResId); mFeedbackSubtitleView.setText(subtitleResId); + mFeedbackTitleView.postDelayed(mTitleViewCallback, mAccessibilityManager + .getRecommendedTimeoutMillis( + FEEDBACK_ANIMATION_MS, + AccessibilityManager.FLAG_CONTENT_TEXT)); + mFeedbackSubtitleView.postDelayed(mSubtitleViewCallback, mAccessibilityManager + .getRecommendedTimeoutMillis( + SUBTITLE_ANNOUNCE_DELAY_MS, + AccessibilityManager.FLAG_CONTENT_TEXT)); + if (isGestureSuccessful) { if (mTutorialFragment.isAtFinalStep()) { showActionButton(); @@ -467,6 +488,7 @@ abstract class TutorialController implements BackGestureAttemptCallback, mFakeTaskbarViewCallback = null; } mFeedbackTitleView.removeCallbacks(mTitleViewCallback); + mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback); } private void playFeedbackAnimation() { @@ -542,6 +564,13 @@ abstract class TutorialController implements BackGestureAttemptCallback, mSkipButton.setVisibility(GONE); mDoneButton.setVisibility(View.VISIBLE); mDoneButton.setOnClickListener(this::onActionButtonClicked); + mDoneButton.postDelayed(() -> { + mDoneButton.requestFocus(); + mDoneButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); + }, mAccessibilityManager + .getRecommendedTimeoutMillis( + DONE_BUTTON_ANNOUNCE_DELAY_MS, + AccessibilityManager.FLAG_CONTENT_CONTROLS)); } void hideFakeTaskbar(boolean animateToHotseat) {