Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ Backtalk doesn't stop speech when something covers the proximity sensor. To turn

In **Verbosity** > **Screen on and off**, you can choose what Backtalk says when the screen turns off, when it turns on, and when you unlock the phone. When the screen turns off, Backtalk says "Screen off" and the ringer mode. When the screen turns on, it says the time, and it can also say the battery, the Wi-Fi network, the mobile network, the ringer and Do Not Disturb, and airplane mode, after the time in the same announcement. When you unlock the phone, it says "Device unlocked". Each of these has its own switch. By default, everything except the extra screen-on items is on.

On phones, at the end of **When the screen turns on**, **Say only the items above** limits automatic System UI lock-screen wake speech to the time and status items you selected. It skips extra lock-screen information, such as the carrier and Extend Unlock, and the automatically restored lock-screen focused item. Focus still returns, and swiping or touching items speaks normally. With no screen-on items selected, the initial lock-screen wake announcement is silent. This option is off by default and is hidden and inactive on Wear OS. The separate **Say "Device unlocked"** switch still controls the unlock announcement.

Some phones send several lock-screen announcements and focus events as they wake. Suppression ends when the device unlocks, you start interacting, the screen turns off, or three seconds pass. System UI and keyguard are identified using Android's configuration, rather than device-specific package names. Unlocked wakes, other apps, incoming calls, alarms, timers, emergency alerts, notifications, and unknown window owners keep their normal speech. The selected time and status announcements still speak normally. Device testing has covered a Samsung Galaxy S23 Plus and a Galaxy S5 running Lineage OS 20; behavior on non-Samsung phones, including Pixels, has not been verified.

These switches are separate from **Status readout**, so the status gesture can say different things. Backtalk doesn't make the screen-on announcement during calls, and the lock screen no longer cuts off "Screen off".

## Pausing Backtalk
Expand Down
22 changes: 22 additions & 0 deletions lint-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25519,5 +25519,27 @@




<issue
id="MissingTranslation"
message='"title_pref_suppress_extra_screen_on_speech" is not translated in "de" (German), "hi" (Hindi), "lo" (Lao), "pt" (Portuguese), "fil" (Filipino; Pilipino), "lt" (Lithuanian), "hr" (Croatian), "lv" (Latvian), "hu" (Hungarian), "hy" (Armenian), "uk" (Ukrainian), "id" (Indonesian), "ur" (Urdu), "mk" (Macedonian), "ml" (Malayalam), "mn" (Mongolian), "af" (Afrikaans), "in" (Indonesian), "mr" (Marathi), "uz" (Uzbek), "ms" (Malay), "el" (Greek), "en" (English), "is" (Icelandic), "it" (Italian), "am" (Amharic), "my" (Burmese), "es" (Spanish), "iw" (Hebrew), "zh" (Chinese), "et" (Estonian), "eu" (Basque), "ar" (Arabic), "as" (Assamese), "vi" (Vietnamese), "nb" (Norwegian Bokmål), "ja" (Japanese), "ne" (Nepali), "az" (Azerbaijani), "fa" (Persian), "zu" (Zulu), "ro" (Romanian), "nl" (Dutch), "no" (Norwegian), "be" (Belarusian), "fi" (Finnish), "ru" (Russian), "bg" (Bulgarian), "bn" (Bangla), "fr" (French), "bs" (Bosnian), "ka" (Georgian), "si" (Sinhala), "sk" (Slovak), "sl" (Slovenian), "ca" (Catalan), "sq" (Albanian), "sr" (Serbian), "kk" (Kazakh), "km" (Khmer), "kn" (Kannada), "or" (Odia), "sv" (Swedish), "ko" (Korean), "sw" (Swahili), "gl" (Galician), "ta" (Tamil), "gu" (Gujarati), "ky" (Kyrgyz), "pa" (Punjabi), "te" (Telugu), "th" (Thai), "tl" (Tagalog), "pl" (Polish), "da" (Danish), "tr" (Turkish)'
errorLine1=' &lt;string name="title_pref_suppress_extra_screen_on_speech"&gt;Say only the items above&lt;/string&gt;'
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="331"
column="13"/>
</issue>

<issue
id="MissingTranslation"
message='"summary_pref_suppress_extra_screen_on_speech" is not translated in "de" (German), "hi" (Hindi), "lo" (Lao), "pt" (Portuguese), "fil" (Filipino; Pilipino), "lt" (Lithuanian), "hr" (Croatian), "lv" (Latvian), "hu" (Hungarian), "hy" (Armenian), "uk" (Ukrainian), "id" (Indonesian), "ur" (Urdu), "mk" (Macedonian), "ml" (Malayalam), "mn" (Mongolian), "af" (Afrikaans), "in" (Indonesian), "mr" (Marathi), "uz" (Uzbek), "ms" (Malay), "el" (Greek), "en" (English), "is" (Icelandic), "it" (Italian), "am" (Amharic), "my" (Burmese), "es" (Spanish), "iw" (Hebrew), "zh" (Chinese), "et" (Estonian), "eu" (Basque), "ar" (Arabic), "as" (Assamese), "vi" (Vietnamese), "nb" (Norwegian Bokmål), "ja" (Japanese), "ne" (Nepali), "az" (Azerbaijani), "fa" (Persian), "zu" (Zulu), "ro" (Romanian), "nl" (Dutch), "no" (Norwegian), "be" (Belarusian), "fi" (Finnish), "ru" (Russian), "bg" (Bulgarian), "bn" (Bangla), "fr" (French), "bs" (Bosnian), "ka" (Georgian), "si" (Sinhala), "sk" (Slovak), "sl" (Slovenian), "ca" (Catalan), "sq" (Albanian), "sr" (Serbian), "kk" (Kazakh), "km" (Khmer), "kn" (Kannada), "or" (Odia), "sv" (Swedish), "ko" (Korean), "sw" (Swahili), "gl" (Galician), "ta" (Tamil), "gu" (Gujarati), "ky" (Kyrgyz), "pa" (Punjabi), "te" (Telugu), "th" (Thai), "tl" (Tagalog), "pl" (Polish), "da" (Danish), "tr" (Turkish)'
errorLine1=' &lt;string name="summary_pref_suppress_extra_screen_on_speech"&gt;When the lock screen turns on, announce only the selected items.&lt;/string&gt;'
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="333"
column="13"/>
</issue>

</issues>
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
import com.google.android.accessibility.talkback.focusmanagement.interpreter.ScreenStateMonitor;
import com.google.android.accessibility.talkback.focusmanagement.interpreter.TouchExplorationInterpreter;
import com.google.android.accessibility.talkback.focusmanagement.record.AccessibilityFocusActionHistory;
import com.google.android.accessibility.talkback.focusmanagement.record.FocusActionInfo;
import com.google.android.accessibility.talkback.gesture.GestureController;
import com.google.android.accessibility.talkback.gesture.GestureHistory;
import com.google.android.accessibility.talkback.gesture.GestureShortcutMapping;
Expand Down Expand Up @@ -217,6 +218,7 @@
import com.google.android.accessibility.talkback.monitor.KeyboardLockMonitor;
import com.google.android.accessibility.talkback.monitor.ProximitySensorMonitor;
import com.google.android.accessibility.talkback.monitor.RingerModeAndScreenMonitor;
import com.google.android.accessibility.talkback.monitor.ScreenOnSpeechSuppression;
import com.google.android.accessibility.talkback.monitor.VolumeMonitor;
import com.google.android.accessibility.talkback.pause.PauseController;
import com.google.android.accessibility.talkback.preference.PreferencesActivityUtils;
Expand Down Expand Up @@ -821,6 +823,8 @@ public interface GestureDetectionState {
private EventLatencyLogger eventLatencyLogger;

private UserInterface userInterface;
private @Nullable ScreenOnSpeechSuppression screenOnSpeechSuppression;

private DisableTalkBackDialog disableTalkbackDialog;

private Configuration lastConfiguration;
Expand Down Expand Up @@ -936,6 +940,9 @@ public void onDestroy() {
if (userInterface != null) {
userInterface.unregisterAllListeners();
}
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.shutdown();
}

if (eventLatencyLogger != null) {
eventLatencyLogger.destroy();
Expand Down Expand Up @@ -1085,6 +1092,9 @@ private boolean didScreenPropertiesChange(Configuration oldConfig, Configuration

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.onAccessibilityEvent(event);
}
// Paused Backtalk drops events, except the end of a touch that started before the pause, so
// that nothing still thinks a finger is down after resuming. Its feedback is dropped too.
if (PauseController.isPaused()
Expand Down Expand Up @@ -1314,6 +1324,9 @@ && switch (physicalKeyboardEcho) {
*/
@Override
protected final boolean onKeyEvent(KeyEvent keyEvent) {
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.onKeyEvent(keyEvent);
}
boolean result = onKeyEventInternal(keyEvent);

if (primesController != null) {
Expand Down Expand Up @@ -1498,6 +1511,9 @@ private void onGestureDebug(GestureAnalyticsEvent analyticsEvent) {

/** Called by {@link TouchInteractionMonitor} when gesture detection started. */
public void onGestureDetectionStarted() {
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.onUserInteraction();
}
if (processorPhoneticLetters != null) {
processorPhoneticLetters.cancelPhoneticLetter(EVENT_ID_UNTRACKED);
}
Expand All @@ -1507,6 +1523,9 @@ private boolean handleOnGestureById(int displayId, int gestureId) {
if (!isServiceActive() || PauseController.isPaused()) {
return false;
}
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.onUserInteraction();
}
// Return before feedback, training, menus and gesture recording. Both callback overloads
// use this entry point; ignoring an action in GestureController still reports it as handled.
if (VibrationWatchGestureSettings.shouldReserve(
Expand Down Expand Up @@ -1938,6 +1957,9 @@ private void initializeFingerprintGestureCallback() {
@Override
public void onGestureDetected(int gesture) {
if (isServiceActive() && !PauseController.isPaused() && gestureController != null) {
if (screenOnSpeechSuppression != null) {
screenOnSpeechSuppression.onUserInteraction();
}
Performance perf = Performance.getInstance();
EventId eventId = perf.onFingerprintGestureEventReceived(gesture);

Expand Down Expand Up @@ -1976,6 +1998,17 @@ private void initializeInfrastructure() {
// not unpaired
// supportsTouchScreen = packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
displayMonitor = new DisplayMonitor(this);
screenOnSpeechSuppression =
ScreenOnSpeechSuppression.create(
this,
prefs,
getSystemService(android.hardware.display.DisplayManager.class)
.getDisplay(Display.DEFAULT_DISPLAY).getState()
== Display.STATE_ON,
FormFactorUtils.isAndroidWear());
screenOnSpeechSuppression.registerUnlockReceiver(this);
// Register first so wake feedback and focus listeners observe the same display transition.
displayMonitor.addDisplayStateChangedListener(screenOnSpeechSuppression);
accessibilityEventProcessor = new AccessibilityEventProcessor(this, displayMonitor);
feedbackController = new FeedbackController(this);
feedbackController.setSoundHaptics(SoundVibrations.patternIds(this));
Expand Down Expand Up @@ -2099,6 +2132,7 @@ public long getSpeechCacheWindowCounts() {
globalVariables);

userInterface = new UserInterface();
userInterface.registerListener(screenOnSpeechSuppression);
userInterface.registerListener(focuser);

inputDeviceMonitor = new InputDeviceMonitor(this);
Expand Down Expand Up @@ -2399,6 +2433,7 @@ public long getSpeechCacheWindowCounts() {
displayMonitor,
statusReader,
this);
ringerModeAndScreenMonitor.addScreenChangedListener(screenOnSpeechSuppression);
if (speechCacheController != null) {
ringerModeAndScreenMonitor.addScreenChangedListener(speechCacheController);
}
Expand Down Expand Up @@ -2462,14 +2497,20 @@ public void onPausedChanged(boolean paused) {
eventFilter =
new EventFilter(this, compositor, touchMonitor, globalVariables, earlyFocusSpeech);
focuser.setFocusSetListener(
(node, info, eventId, actionTime) ->
eventFilter.onAccessibilityFocusSet(
node,
info,
eventId,
actionTime,
/* continuousReading= */ fullScreenReadActor != null
&& fullScreenReadActor.isActive()));
(node, info, eventId, actionTime) -> {
screenOnSpeechSuppression.onFocusAction(info);
FocusActionInfo speechInfo =
screenOnSpeechSuppression.shouldSuppressFocus(node, info)
? new FocusActionInfo.Builder(info).setForceMuteFeedback(true).build()
: info;
eventFilter.onAccessibilityFocusSet(
node,
speechInfo,
eventId,
actionTime,
/* continuousReading= */ fullScreenReadActor != null
&& fullScreenReadActor.isActive());
});
eventFilter.setTargetPredictor(directionNavigationActor::predictTarget);
eventFilter.setFingerDownSupplier(
() ->
Expand Down Expand Up @@ -2683,6 +2724,10 @@ public WindowEventInterpreter getWindowEventInterpreter() {
return windowEventInterpreter;
}

public @Nullable ScreenOnSpeechSuppression getScreenOnSpeechSuppression() {
return screenOnSpeechSuppression;
}

private final TouchInteractingIndicator touchInteractingIndicator =
new TouchInteractingIndicator() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.google.android.accessibility.talkback.compositor.roledescription.RoleDescriptionExtractor;
import com.google.android.accessibility.talkback.eventprocessor.ProcessorPhoneticLetters;
import com.google.android.accessibility.talkback.imagecaption.ImageContents;
import com.google.android.accessibility.talkback.monitor.ScreenOnSpeechSuppression;
import com.google.android.accessibility.talkback.scripting.Scripts;
import com.google.android.accessibility.utils.AccessibilityEventUtils;
import com.google.android.accessibility.utils.Performance.EventId;
Expand Down Expand Up @@ -501,6 +502,14 @@ private void handleEvent(
@Nullable EventId eventId,
HandleEventOptions options,
LoadSpeechResultNotifier synthesizeResultNotifier) {
// Selection and text interpreters can bypass EventFilter. Apply the wake policy at the common
// output boundary, after event interpretation/state updates, for every raw-event speech path.
ScreenOnSpeechSuppression wakeSuppression = ScreenOnSpeechSuppression.from(mContext);
if (options.eventObject != null
&& wakeSuppression != null
&& wakeSuppression.shouldSuppressEvent(options.eventObject)) {
return;
}
// Extract options.
@Nullable EventInterpretation eventInterpretation = options.eventInterpretation;
if (eventInterpretation != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.google.android.accessibility.talkback.eventprocessor.EventState;
import com.google.android.accessibility.talkback.eventprocessor.ProcessorAccessibilityHints;
import com.google.android.accessibility.talkback.flags.FeatureFlagReader;
import com.google.android.accessibility.talkback.monitor.ScreenOnSpeechSuppression;
import com.google.android.accessibility.talkback.gesture.GestureHints;
import com.google.android.accessibility.talkback.gesture.GestureShortcutMapping;
import com.google.android.accessibility.utils.AccessibilityEventListener;
Expand Down Expand Up @@ -332,6 +333,12 @@ public Feedback composeFeedback(

Feedback feedback = new Feedback();

// Only verified lock-screen System UI feedback is quiet; mixed/unknown windows still speak.
ScreenOnSpeechSuppression wakeSuppression = ScreenOnSpeechSuppression.from(service);
if (wakeSuppression != null && wakeSuppression.shouldSuppressWindow(interpretation)) {
return feedback;
}

if (isWakeUpForWear(interpretation)) {
// Only announce unread notification feedback on wake up experience for wear and have early
// return to avoid unwanted window title announcement.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ private void speakForDisplayOn(EventId eventId) {
}
}

if (ttsText.length() == 0) {
return;
}

SpeakOptions speakOptions =
SpeakOptions.create()
// Uses QUEUE_MODE_UNINTERRUPTIBLE_BY_NEW_SPEECH so that time announcement does not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ object ScreenAnnouncementSettings {
const val SAY_SCREEN_OFF = "pref_screen_off_say_screen_off"
const val SCREEN_OFF_RINGER = "pref_screen_off_ringer_mode"
const val SAY_UNLOCKED = "pref_unlock_say_unlocked"
const val SUPPRESS_EXTRA_SCREEN_ON_SPEECH = "pref_screen_on_suppress_extra_speech"

/**
* The status items that can be spoken after the time as the screen turns on, in the order they
Expand All @@ -45,7 +46,12 @@ object ScreenAnnouncementSettings {

/** Each setting's default, which is what Backtalk said before these settings existed. */
val DEFAULTS: Map<String, Boolean> =
mapOf(SAY_SCREEN_OFF to true, SCREEN_OFF_RINGER to true, SAY_UNLOCKED to true) +
mapOf(
SAY_SCREEN_OFF to true,
SCREEN_OFF_RINGER to true,
SAY_UNLOCKED to true,
SUPPRESS_EXTRA_SCREEN_ON_SPEECH to false,
) +
SCREEN_ON_STATUS.values.associateWith { false }

@JvmStatic
Expand Down
Loading
Loading