Skip to content

Restore previous accessibility focus when going Back - #151

Open
willbilec wants to merge 2 commits into
trypsynth:masterfrom
willbilec:codex/restore-focus-on-back
Open

willbilec wants to merge 2 commits into
trypsynth:masterfrom
willbilec:codex/restore-focus-on-back

Conversation

@willbilec

@willbilec willbilec commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Returning with Back can lose accessibility focus when an app rebuilds its list or a browser reconstructs a page. Backtalk now remembers the last focused item on up to 20 screens during the current service session and restores a strong identity/content match when returning, including after opening another app.

Restoration is Back-only and phone-only. User input cancels it; weak or missing matches fall back to existing initial focus. Visible-item restoration is always active. The Advanced Settings option Scroll to restore previous focus is disabled by default and permits at most ten supported scroll actions within a three-second restoration deadline. Ordinary TalkBack window restoration remains unchanged on Wear and TV.

Addresses the maintainer review: ordinary focus tracking caches a lightweight identity, detailed bookmarks are captured at activation or Back, idle content changes and hover cancellation perform no window-root reads, and each restore attempt reads context once. Ancestor/sibling work is bounded. Departing focus uses its own window for split-screen support; Back controls use resource IDs and localized Navigate-up descriptions. The setting is hidden on Wear/TV, Czech translations are included, and documentation is under Navigation. Rebased onto master to preserve the direct-touch fix and restored build/test workflow.

Validation:

  • testPhoneDebugUnitTest: 667 tests passed.
  • :talkback:testWearDebugUnitTest: 479 tests passed.
  • 120 focus tests cover matching, reconstruction, cancellation, scrolling, and counted window access with real accessibility trees.
  • :lintPhoneDebug, assemblePhoneDebug, assembleWearDebug, and git diff --check passed.
  • The user previously confirmed RedReader, Edge, Chrome, and Firefox behavior and confirmed the installed revised isolated debug APK still works. Individual app cases were not separately reported for this latest revision.

Debug configuration, APKs, local guidance, and unrelated session work are excluded. The separate suppressed-speech PR is untouched.

@trypsynth trypsynth left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is a nice idea, and matching the item by its content when an app rebuilds the screen does something TalkBack's own restore can't. Before it can merge, the cost needs to come down a lot, because it runs all the time rather than only around a Back.

Must fix: main-thread work (ScreenFocusRestorer.kt)

  • Every TYPE_WINDOW_CONTENT_CHANGED calls schedule() (around :266), and each run calls observeScreen(context()) before checking whether a restore is pending (:379-380). context() reads rootInActiveWindow and walks up to 100 nodes. During any animation, progress bar or chat that's several walks a second, in every app, with no Back pending.
  • onUserInteraction() calls context() whenever a retry is queued, and it runs on every hover enter, so touch exploration pays for it.
  • Each swipe walks the tree two or three times, and describe() loops through up to 100 siblings per level. rememberBeforeActivation does this before every click, which delays activation.
  • None of this is turned off on watches, where these calls are slowest (see #152).

Suggestions: do nothing on content changes unless a Back is pending; keep the focused node and only describe it when Back or an activation actually happens; skip context() in onUserInteraction and on hover; and turn the feature off on watches.

Should fix

  • isBackControl matches the English labels "Back" and "Navigate up" (around :171), so app Back buttons aren't recognised in other languages. Matching by resource ID, or by the framework's localized "Navigate up" string, would work everywhere.
  • In split screen, focus outside rootInActiveWindow cancels the attempt and is never saved.
  • The tests replace readContext with a stub, so the tree walking and the app calls, which are the costly and risky part, aren't covered.
  • The differences.md section sits above the intro paragraph; it belongs under Navigation. The summary string's "Off by default." and "allow Backtalk to" don't follow Google style, and the Czech strings are missing from values-cs/strings_backtalk.xml.
  • CI on this PR ran while master's workflow was broken (lint only), so it was never built or unit tested there. A rebase onto current master will run the full workflow.

@willbilec
willbilec force-pushed the codex/restore-focus-on-back branch from 7eeb5e6 to 644aa99 Compare October 11, 2026 21:42
@willbilec

Copy link
Copy Markdown
Contributor Author

Thanks for going through this. I've addressed the points in your review in commit 644aa99:

  • Idle content changes no longer read window roots or walk the tree. Restoration checks that Back is active and within its deadline before reading a root. Hover and user-interaction cancellation also avoid window reads.
  • Ordinary focus tracking now caches the focused node with a lightweight identity snapshot. Detailed bookmarks are built immediately before activation or Back, with one context read per restoration attempt. Description work is limited to eight ancestors and 100 total sibling visits.
  • The feature and its listeners are disabled on Wear and TV, and the setting is hidden there. Ordinary TalkBack window restoration is retained.
  • Back controls use resource IDs and localized Navigate-up descriptions instead of English labels. The departing screen is read from the focused node's own window, including in split screen.
  • Added 16 tests using real accessibility trees through a counted window-access boundary. These cover idle events, hover cancellation, focus tracking, Wear behavior, split screen, localized controls, recycled nodes, automatic focus, and delayed browser reconstruction. Existing matching and scrolling coverage is retained.
  • Moved the documentation under Navigation, updated the summary to “Scroll to the last focused item when returning to a screen.”, and added Czech translations and translator comments. Only the relevant translation-baseline entries changed.
  • Rebased onto master, preserving the direct-touch fix and restored full build/test workflow.

Validation passed: 667 phone unit tests, 479 TalkBack Wear unit tests, including 120 focus tests; :lintPhoneDebug; phone and Wear debug builds; and git diff --check.

I tested the revised isolated debug APK and it still appears to work. RedReader, Edge, Chrome, and Firefox had also been confirmed during the earlier testing, though I haven't separately reported every app case for this latest revision. Restoration remains Back-only, scrolling stays off by default, and user input cancels pending restoration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants