From 31517150ad68a2b2caec25886db762c650cadc45 Mon Sep 17 00:00:00 2001 From: Quin Gillespie Date: Mon, 26 Oct 2026 06:59:58 -0600 Subject: [PATCH 1/2] Give watches before Android 12 fixed Wear material colors The Wear material colors pointed at the system's dynamic colors, which only exist from Android 12, and the watch's values-watch-v30 copy applied them on Wear OS 3 too, where the training list items that use them could fail to load. The base colors are now close fixed colors, and values-v31 uses the dynamic ones. Without the watch override, Android 12 and later watches still get the dynamic colors, since a watch qualifier would outrank values-v31. --- lint-baseline.xml | 319 ------------------ .../src/wear/res/values-watch-v30/colors.xml | 30 -- utils/src/main/res/values-v31/colors.xml | 11 +- utils/src/main/res/values/colors.xml | 21 +- 4 files changed, 21 insertions(+), 360 deletions(-) delete mode 100644 talkback/src/wear/res/values-watch-v30/colors.xml diff --git a/lint-baseline.xml b/lint-baseline.xml index b8c648e41..2c52a5c5e 100644 --- a/lint-baseline.xml +++ b/lint-baseline.xml @@ -10652,105 +10652,6 @@ column="12"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @android:color/system_neutral1_900 - @android:color/system_neutral1_50 - @android:color/system_accent1_600 - @android:color/system_neutral2_700 - @android:color/system_neutral2_200 - @android:color/system_neutral2_200 - @android:color/system_neutral1_10 - @android:color/system_neutral1_800 - @android:color/system_neutral1_800 - @color/google_black - @color/google_white - diff --git a/utils/src/main/res/values-v31/colors.xml b/utils/src/main/res/values-v31/colors.xml index 34008b0b9..a6fde95be 100644 --- a/utils/src/main/res/values-v31/colors.xml +++ b/utils/src/main/res/values-v31/colors.xml @@ -15,5 +15,14 @@ limitations under the License. --> - + + @android:color/system_neutral1_900 + @android:color/system_neutral1_50 + @android:color/system_accent1_600 + @android:color/system_neutral2_700 + @android:color/system_neutral2_200 + @android:color/system_neutral2_200 + @android:color/system_neutral1_10 + @android:color/system_neutral1_800 + @android:color/system_neutral1_800 diff --git a/utils/src/main/res/values/colors.xml b/utils/src/main/res/values/colors.xml index be70f4530..55d460702 100644 --- a/utils/src/main/res/values/colors.xml +++ b/utils/src/main/res/values/colors.xml @@ -43,16 +43,17 @@ @color/google_black - - @android:color/system_neutral1_900 - @android:color/system_neutral1_50 - @android:color/system_accent1_600 - @android:color/system_neutral2_700 - @android:color/system_neutral2_200 - @android:color/system_neutral2_200 - @android:color/system_neutral1_10 - @android:color/system_neutral1_800 - @android:color/system_neutral1_800 + + @color/google_grey900 + @color/google_grey50 + @color/google_blue600 + @color/google_grey700 + @color/google_grey200 + @color/google_grey200 + @color/google_grey50 + @color/google_grey800 + @color/google_grey800 @color/google_black @color/google_white From b423149e1d7f23c73f1f1781112c4e371ea7aebb Mon Sep 17 00:00:00 2001 From: Quin Gillespie Date: Mon, 26 Oct 2026 13:19:59 -0600 Subject: [PATCH 2/2] Stop speech when touch focuses an item, with Stop speech when touching the screen off With the switch off, touching no longer stopped speech, and focusing an item by touch only interrupted speech that new speech can interrupt. Notifications, toasts and other uninterruptible speech went on, and the touched item waited behind them, so exploring seemed slow and silent. Now focusing an item by touch stops all speech, as touching did with the switch on. Fixes #282 --- .../focusmanagement/FocusFeedbackMapper.java | 17 +++- .../FocusFeedbackMapperTouchTest.kt | 80 +++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 talkback/src/test/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapperTouchTest.kt diff --git a/talkback/src/main/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapper.java b/talkback/src/main/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapper.java index a5643868e..8b4e9a508 100644 --- a/talkback/src/main/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapper.java +++ b/talkback/src/main/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapper.java @@ -120,17 +120,17 @@ public static void setTouchStopsSpeech(boolean stops) { Feedback.sound(R.raw.view_entered).vibration(R.array.view_hovered_pattern).build()); } case TOUCH_START, TOUCH_ENTERED_UNFOCUSED_NODE -> { - // Speech of an item that touch focuses still interrupts what was being said. + // Without this, speech stops when touch focuses an item instead, below. return touchStopsSpeech ? Feedback.create(eventId, Feedback.part().setInterruptGentle(true).build()) : null; } case TOUCH_FOCUSED_NODE -> { - return toFeedback( + return toTouchFocusFeedback( eventId, Feedback.focus(FOCUS_FOR_TOUCH).setTarget(touchTarget).setForceRefocus(true)); } case TOUCH_UNFOCUSED_NODE -> { - return toFeedback(eventId, Feedback.focus(FOCUS_FOR_TOUCH).setTarget(touchTarget)); + return toTouchFocusFeedback(eventId, Feedback.focus(FOCUS_FOR_TOUCH).setTarget(touchTarget)); } case LIFT -> { return toFeedback(eventId, Feedback.focus(CLICK_NODE).setTarget(touchTarget)); @@ -154,6 +154,17 @@ private static Feedback toFeedback(@Nullable EventId eventId, Focus.Builder focu return Feedback.create(eventId, Feedback.part().setFocus(focus.build()).build()); } + /** + * Focuses an item by touch. When touching doesn't stop speech, focusing the item does, as touching + * would have: notifications, toasts and other speech that new speech can't interrupt would + * otherwise keep the item from being spoken until they finish. + */ + private static Feedback toTouchFocusFeedback(@Nullable EventId eventId, Focus.Builder focus) { + return Feedback.create( + eventId, + Feedback.part().setInterruptGentle(!touchStopsSpeech).setFocus(focus.build()).build()); + } + /** Feedback-mapping function. */ public static Part.@Nullable Builder onNodeManuallyScrolled( Variables variables, int depth, FocusFinder focusFinder) { diff --git a/talkback/src/test/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapperTouchTest.kt b/talkback/src/test/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapperTouchTest.kt new file mode 100644 index 000000000..207c1708c --- /dev/null +++ b/talkback/src/test/java/com/google/android/accessibility/talkback/focusmanagement/FocusFeedbackMapperTouchTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2026 Backtalk contributors + * + * 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.google.android.accessibility.talkback.focusmanagement + +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.google.android.accessibility.talkback.Feedback +import com.google.android.accessibility.talkback.Interpretation +import com.google.android.accessibility.talkback.Interpretation.Touch.Action +import com.google.android.accessibility.talkback.Mappers +import com.google.android.accessibility.utils.Performance.EVENT_ID_UNTRACKED +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class FocusFeedbackMapperTouchTest { + private val node = AccessibilityNodeInfoCompat.obtain() + + @After + fun restoreDefault() { + FocusFeedbackMapper.setTouchStopsSpeech(true) + } + + @Test + fun touchStopsSpeech_stopsItOnTouch_notOnFocus() { + FocusFeedbackMapper.setTouchStopsSpeech(true) + assertTrue(part(Action.TOUCH_START)!!.interruptGentle()) + assertTrue(part(Action.TOUCH_ENTERED_UNFOCUSED_NODE)!!.interruptGentle()) + assertFalse(part(Action.TOUCH_UNFOCUSED_NODE)!!.interruptGentle()) + assertFalse(part(Action.TOUCH_FOCUSED_NODE)!!.interruptGentle()) + } + + @Test + fun touchDoesNotStopSpeech_stopsItWhenTouchFocusesAnItem() { + FocusFeedbackMapper.setTouchStopsSpeech(false) + assertNull(part(Action.TOUCH_START)) + assertNull(part(Action.TOUCH_ENTERED_UNFOCUSED_NODE)) + // Speech that new speech can't interrupt, such as a notification, would otherwise keep the + // item from being spoken. + val focus = part(Action.TOUCH_UNFOCUSED_NODE)!! + assertTrue(focus.interruptGentle()) + assertNotNull(focus.focus()) + assertTrue(part(Action.TOUCH_FOCUSED_NODE)!!.interruptGentle()) + } + + private fun part(action: Action): Feedback.Part? { + val variables = + Mappers.Variables( + RuntimeEnvironment.getApplication(), + /* event= */ null, + Interpretation.Touch.create(action, node), + /* monitors= */ null, + ) + return FocusFeedbackMapper.mapTouchToFocusAction(EVENT_ID_UNTRACKED, variables, 0) + ?.failovers() + ?.first() + } +}