Skip to content

TalkBack: app accessibility combined implementation (temporary) - #6112

Closed
eliotcougar wants to merge 1 commit into
2dust:masterfrom
eliotcougar:fix/talkback-subscription-rows
Closed

eliotcougar wants to merge 1 commit into
2dust:masterfrom
eliotcougar:fix/talkback-subscription-rows

Conversation

@eliotcougar

@eliotcougar eliotcougar commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Status

This draft has been rebuilt directly on current upstream/master (13138ddd, v2.3.6). Its history is now one accessibility-only commit: 17312c04.

The obsolete pre-merge #6105/service-transition commits are no longer part of this branch. The merged #6105 group paging and #6107 server-row model architecture are inherited from upstream rather than replayed here.

Summary

  • Improve TalkBack semantics for subscriptions, routing rules, asset files, settings, per-app routing, server rows, group tabs, dialogs, actions, confirmations, and important transient results.
  • Keep secondary details such as subscription URLs and package IDs out of merged row announcements while retaining useful localized dates, plurals, routing context, named actions, and named deletion confirmations.
  • Use genuine Compose clickable, toggleable, selectable, checkbox, switch, radio-button, and tab semantics instead of rebuilding control roles, state, and actions by hand.
  • Keep disabled settings as native disabled controls and group each row into one logical accessibility target.
  • Remove accessibility-focus restoration and direct TYPE_ANNOUNCEMENT dispatch. Important foreground transient results use a polite Compose live region while the visual Snackbar/Toast surface remains outside accessibility navigation.
  • Keep all introduced accessibility resources aligned across the nine maintained catalogs.

Upstream conflict resolution

  • Preserved Compose: prepare server rows outside composition #6107's ServerRowUiModel and row-action architecture; the delete action passes the existing ProfileItem only so the confirmation can retain the server name.
  • Preserved upstream's service lifecycle and restart behavior; no service-transition implementation files are included in this PR.
  • Adapted selected server rows, group tabs, and connection-test output to native selectable/state semantics without reintroducing focus manipulation.
  • Retained the merged Compose: improve group paging and connection-test results #6105 one-final-result connection-test flow while keeping an immediate localized Testing state and localized plural delay units for accessibility.

Accessibility implementation policy

The branch now follows the official Android/Compose approach described in the detailed policy comment below: expose correct labels, roles, states, actions, grouping, and enabled/disabled status, then let the accessibility service choose spoken order. Native role announcements are retained even when TalkBack appends words such as “switch”, “radio button”, or “tab” at the end.

This remains a broad consolidation draft. The next publication step is to split its independently reviewable behavior into smaller focused PRs without reintroducing the discarded semantic workarounds.

Validation

  • :app:processPlaystoreDebugResources
  • :app:compilePlaystoreDebugKotlin
  • full :app:testPlaystoreDebugUnitTest
  • all 39 current changed resources present in every maintained locale catalog
  • merge-marker, obsolete focus/announcement symbol, service-scope, and git diff --check audits

@aliRahimi1997

aliRahimi1997 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thanks for the accessibility improvements in
#6112
I tested the latest commit on a real device with both TalkBack and Commentary.

Overall, the changes work well, and I did not notice any other major accessibility problems. I found only a few remaining issues:

  1. Switches in Settings and Per-app Proxy

All switches currently require two swipes/focus stops:

  • The first swipe reads the setting name, but the switch itself is not announced and cannot be activated.
  • The second swipe reads the same setting again, followed by “is currently on/off”, and the switch can then be activated.

I think this could be simplified to a single accessible element, for example:

“Setting name, On”
or
“Setting name, Off”

There is no need for “is currently on/off” because simply announcing “On” or “Off” is enough. The current behavior also unnecessarily doubles the number of swipes needed to navigate through the settings.

The good part is that after changing the switch, the new On/Off state is announced correctly, so that part works very well.

The same behavior occurs in Per-app Proxy for the two switchable options. The application names and checkboxes in that screen work correctly, including announcing their checked/unchecked state.

  1. Routing rule deletion confirmation
    When deleting a routing rule, the confirmation dialog asks for confirmation but does not include the name of the rule being deleted.

Also, I noticed that the Delete action is available inside the Edit screen. I am not sure whether a Delete action is also supposed to be available directly on the rule row in the list, because TalkBack does not announce a Delete action there. I can only confirm that the Delete action is accessible after entering the Edit screen.

If Delete is intended to be available directly from the list, it may be worth checking whether it is exposed correctly to screen readers.

Apart from these points, the other areas I tested, including User Assets and the general Subscription/Routing navigation, worked well for me.

Overall,
#6112
looks very good from my testing. These are just the remaining issues I noticed.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

This PR has grown quite a bit, but it now addresses everything except the Edit server/subscription group/routing rule/asset Activities, and the main server list that is covered separately by #6105. It is almost done as well.

The PR may be big in terms of lines of code, but it does very similar things to all interactive elements in the main Activities. Namely, it constructs custom spoken strings for TalkBack to say and sometimes suppresses the stuff that is unimportant or intrusive. Normal navigation is unaffected.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

Thanks for the updates. I tested the latest version of #6112 on a real device with both TalkBack and Commentary, and overall the accessibility behavior is looking much better now.

I went through the areas that were changed in this PR and I did not find any other significant accessibility problems.

Here is what I found:

  1. Settings – Switches

This is the only remaining issue I noticed.

Each Switch now appears twice during swipe navigation, and both entries are independently clickable.

On the first swipe, the screen reader announces the setting name, its description, and then the Switch. On the second swipe, it announces the setting name and its current state (for example, “is on” or “is off”), followed by the Switch.

Both entries actually change the same Switch state when activated, and the state is announced immediately after changing it. So the functionality itself works correctly.

However, having the same Switch exposed twice makes navigation unnecessarily long and confusing. Ideally, each Switch should appear only once in swipe navigation.

The same behavior is also present with the two Switches at the top of the Per-app Proxy screen.

One clarification: the “is on / is off” announcement itself is good and useful. I would keep that. The problem is the duplicate accessibility node/focus, not the state announcement.

  1. Per-app Proxy

The application list is much better now. The CheckBox descriptions are announced clearly, and I did not notice any problem there.

The only issue remaining on this screen is the duplicate swipe behavior of the two Switches mentioned above.

  1. Routing

I tested the Rule list and deletion flow again.

Everything looks good now:

  • The Rule name is announced.
  • The Edit button is accessible.
  • The Delete button in the Rule list is also accessible.
  • When deleting a Rule, the confirmation message includes the Rule name.
  • I did not notice any navigation or announcement problems here.
  1. Subscriptions

I tested the subscription list and deletion flow.

The subscription name is announced correctly, and the deletion confirmation also includes the subscription name.

I did not find any accessibility problem here.

  1. User Assets

The deletion flow is also working correctly. The asset name is included in the confirmation message.

  1. Other Settings and screen titles

I also checked several normal Settings items that are not Switches, as well as different screens/titles.

I did not notice any other duplicate announcements, broken navigation, or missing accessibility information.

I also did not notice any visual/UI changes that would negatively affect sighted users.

Overall, I think the PR is in very good shape now. I tested it quite thoroughly with both TalkBack and Commentary, and the only issue I can still reproduce is the duplicate exposure of Switches during swipe navigation.

So from my side, I would consider the current implementation very close to complete. Thank you for all the work on this PR!

@eliotcougar

eliotcougar commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Ummm... What's the "swipe navigation"?
I guess the problem is the result of me intentionally leaving both the entire row, and the individual switch clickable with slightly different accessibility semantics... I should probably just do mergeDescendants for the row and switch so they act like one control. It's done exactly like that in most places where there's a text and a switch/checkbox.

Nevermind... I figured it out... Swipe navigation...
At this pace I will be ready to be blind in no time…

@aliRahimi1997

Copy link
Copy Markdown
Contributor

By “swipe navigation”, I mean moving between accessibility elements using swipe gestures in TalkBack or Commentary.

Yes, that sounds exactly like the issue I was seeing. The row and the Switch were exposed as two separate focusable elements even though they control the same setting.

Your mergeDescendants approach sounds good to me. I’ll test it carefully with both TalkBack and Commentary once it’s implemented.

Thanks!

@eliotcougar eliotcougar changed the title fix/TalkBack: Custom accessibility semantics for main Activities TalkBack: improve accessibility semantics across main screens Aug 21, 2026
@eliotcougar

Copy link
Copy Markdown
Contributor Author

The first-order Activities' accessibility semantics are fully functional, but it's not pretty yet under the hood. Let me do a couple more cleanup passes before it's ready for merge.
And there are some quirks I need to fix too.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

I tested the latest changes quite thoroughly, and overall the accessibility behavior is now very good. The Switch double-swipe issue is fixed, Per-app Proxy, Routing, Subscriptions, User Assets, and server focus/navigation all work well.

I noticed two small things:

  1. Selected server announcement:
    The selected-server announcement is clear, but “Selected server — France” feels unnecessarily verbose. Since the server name is already announced immediately afterwards, I think simply “Selected — France” would be enough. I noticed commit 8ee15c8 changed this wording across all translations from the shorter form to “Selected server”. Would it be possible to keep the shorter “Selected” wording?

  2. Screen title when entering a page:
    When opening a page such as Settings or Per-app Proxy, TalkBack initially announces the app name, but not the page title. The page title is only announced after the first swipe. I think it would be better if the page title were announced when the screen opens, so the user immediately knows which screen they entered.

One small unrelated suggestion: in the main navigation drawer, TalkBack currently adds the element role “tab” to each item's spoken announcement, for example “Settings, tab” or “Per-app proxy, tab”. If possible, I think it would be cleaner if the spoken announcement contained only the item's name, e.g. simply “Settings” or “Per-app proxy”, without adding “tab”, while keeping the underlying accessibility semantics and navigation behavior intact.

Apart from these points, everything I tested is working very well. The Switch behavior in particular is now much better.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

One more thing I noticed while testing the current versions, unrelated to the accessibility changes in this PR.

The way ordinary messages are currently presented feels more intrusive than it used to. In 2.2.6, these messages felt much simpler and less intrusive, while in the versions after 2.2.6, including the current 2.3.5 pre-release, they are presented using the newer Snackbar-style UI, which appears more like a large notification/card. This is especially noticeable when using TalkBack.

Would it be possible to simplify the presentation of these ordinary messages, perhaps by using a simple Toast-style notification instead of the current Snackbar presentation?

I think this could make the overall experience cleaner and less distracting, especially for TalkBack users. If you think this is a reasonable approach, could you please consider implementing it? I would be happy to test the resulting build and report back on how it behaves with TalkBack.

@eliotcougar

Copy link
Copy Markdown
Contributor Author
  1. Screen title when entering a page:
    When opening a page such as Settings or Per-app Proxy, TalkBack initially announces the app name, but not the page title. The page title is only announced after the first swipe. I think it would be better if the page title were announced when the screen opens, so the user immediately knows which screen they entered.

I already tried to do that, but for some reason TalkBack doesn't say the page title after the app name (the app name announcement seems to be strongly hardcoded). I may try to look into it one more time. A dirty fix would be to change the focusing order. Right now the uppermost button is focused by default, which is usually the "Menu" or "Back" button. The page title is usually displayed next to it in the current UI.

I already started addressing the notification storm caused by simultaneous duplicated toasts and notification updates. My plan is to completely remove most toasts from the accessibility semantics (also they are very annoying because they tend to steal selection focus when they pop up) and replace them with explicit voice-only announcements instead.

The rest is exactly the kinds of issues I plan to address during the cleanup.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

I tested the latest two commits with TalkBack, and everything is working very well.

  • Page titles: when entering Settings or Per-app Proxy, TalkBack announces the app name first and then moves focus to the page title. This works well and immediately makes it clear which screen I'm on.
  • After that, focus moves normally to the first control.
  • Navigation drawer: the role announcements such as “tab” are now gone. Items like Settings and Per-app Proxy are announced cleanly with just their names. This is excellent.
  • Selected server announcement: this is now working correctly as intended, without the unnecessary “selected server” wording.
  • Unselected servers: no unnecessary announcement is added.
  • Drawer branding: no unnecessary branding or decorative elements are announced.
  • Normal TalkBack interaction and double-tap actions are all working correctly in my testing.

Overall, these two commits look very good from a TalkBack user's perspective. Thank you!

@eliotcougar eliotcougar changed the title TalkBack: improve accessibility semantics across main screens TalkBack: improve accessibility across settings screens Aug 21, 2026
@eliotcougar

Copy link
Copy Markdown
Contributor Author

The refinement passes are complete. This PR should have no intersections with #6105. The two can be merged in any order, hopefully. It is better to test them merged together.

As I said earlier, the PR is very large, but it is uniform. It is mostly the same kind of changes in every place.

@eliotcougar
eliotcougar marked this pull request as ready for review August 21, 2026 21:19
aliRahimi1997 added a commit to aliRahimi1997/v2rayNG that referenced this pull request Aug 22, 2026
@aliRahimi1997

Copy link
Copy Markdown
Contributor

I tested PR
#6105
and PR
#6112
separately, and also tested a real merged build containing the changes from both PRs.

Overall, most of the accessibility improvements work correctly. However, two important server-related announcements regress when the changes from the two PRs are combined.

  1. Server testing announcement

In PR
#6112
when I activate server testing, TalkBack immediately announces "Testing..." and then announces the test result. This behavior is clear and works very well for a screen-reader user.

In PR
#6105
this intermediate announcement is missing. On the first test, TalkBack remains completely silent until the final result is available. On subsequent tests, instead of announcing "Testing...", it may announce the existing status, for example:

"Connected, tap to check connection"

The merged build of #6105 + #6112 has the same regression.

The desired behavior is:

Tap Test
immediately announce "Testing..."
announce the final test result

The "Testing..." announcement is important, rather than merely cosmetic. Without it, a screen-reader user receives no immediate auditory feedback that the test has actually started.

  1. Selected server announcement

The actual server selection works correctly. For example, when I tap an unselected server, the server is successfully selected and becomes active.

However, the accessibility announcement is not produced immediately after the selection.

Currently, after selecting a server, TalkBack does not announce the new selected state unless I manually move the accessibility focus somewhere else and then return to the server. After doing that, it correctly announces something equivalent to:

"Selected, "

followed by the server information.

The desired behavior is:

Immediately after activating a different server, TalkBack should announce:

"Selected, "

without requiring the user to move the accessibility focus away and back again.

The server name should of course be the actual name of the selected server; "France" is only an example.

  1. Subscription group navigation delay

When swiping between subscription groups before selecting a group, there is a noticeable delay before the next group is announced.

The delay feels approximately 1–2 seconds, although I cannot measure the exact duration. During this interval, TalkBack is silent.

For example:

Group A
swipe
silence for approximately 1–2 seconds
Group B

Group B is eventually announced correctly, and selecting it works correctly. The issue is therefore mainly responsiveness during navigation rather than a failure to select or announce the destination group.

With the previous subscription-group structure, navigation felt more responsive and did not have this noticeable delay.

  1. Other accessibility behavior that was tested successfully

The following areas were tested and did not show problems in the current combined build:

  • Page titles and their reading order
  • Drawer accessibility
  • Removal of unnecessary "tab" role announcements
  • Decorative v2rayNG branding at the top of the Drawer is not announced by TalkBack
  • Settings that are not switches
  • Disabled settings
  • Subscriptions
  • User Assets
  • Delete actions in lists
  • Delete confirmations containing the relevant rule name
  • Update Subscription Group: the final result is announced correctly
  • Starting and stopping the service
  • Commentary
  • Actual server selection
  • Server information after focus is moved back to the selected server
  1. Check for update

This area may need additional verification.

At the moment there is no newer version available to test the "new version available" case. In the current test, when checking for updates, TalkBack did not announce either that an update was available or that the application was already up to date.

I therefore do not want to classify this as a confirmed regression yet. It may simply require more specific testing of the available-result states.

  1. Merge testing

I also performed a real merge of PR
#6105
and PR
#6112
instead of assuming that their behavior would remain unchanged after merging.

The merge produced conflicts in several localized strings.xml files. These conflicts were resolved manually with the help of AI, so the resulting localized string resources should also be taken into consideration when evaluating the combined behavior.

After resolving the conflicts, the combined build was tested separately.

Importantly, some of the accessibility behavior that worked correctly in
#6112
was no longer preserved in the combined result. In particular, the "Testing..." announcement and the immediate selected-server announcement were affected.

However, I cannot determine with certainty whether these regressions were caused by the code changes themselves, the localized string resources, or the conflict resolution during the merge.

Therefore, the interaction between the two PRs should be tested carefully after merging, including the resulting accessibility strings/resources, rather than assuming that the two PRs are behaviorally compatible simply because each PR can be tested independently.

  1. Persian TalkBack/TTS testing

The accessibility announcements were also tested in Persian with TalkBack.

Persian TTS is not a limitation of this testing environment. The configured TTS engine can correctly handle the Persian accessibility announcements, so the reported issues are not caused by a lack of Persian TTS support.

In summary, the accessibility improvements are largely working well. The main issues I would recommend addressing are:

  • Restore the immediate "Testing..." announcement every time a server test starts.
  • Announce "Selected, " immediately after selecting a server, without requiring focus to be moved away and back.
  • Investigate the noticeable 1–2 second silent delay when swiping between subscription groups.
  • Optionally investigate the accessibility announcement behavior of "Check for update" when no update is available.

The server selection itself is functioning correctly; the main problem is the timing and delivery of the accessibility announcements.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

Thank you very much for extensive testing.

  1. I think I lost the "Testing" announcement when I made it wait until the complete testing result was available, including the country code and IP. That can easily be addressed.
  2. The new server announcement previously was the result of the announcement storm - multiple elements were stealing focus, and in the end the new server was re-selected and announced. Now the toasts are inert, and the announcements are explicit. The focus doesn't leave the element that was tapped last, therefore Talkback doesn't think it needs to read it.
  3. Needs investigation.

I suggest merging it as is for now. It will be easier to address the issues separately.

  1. There is no Persian/Farsi in my TalkBack settings (Pixel 9, Android 17), and it doesn't read anything when that language is selected in the app.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

Thank you for the detailed explanation. That makes sense.

I agree with merging it as is for now and addressing these issues separately.

Regarding the "Testing" announcement, I should mention that this behavior was actually working very well in PR #6112. Each time I tapped the test action, TalkBack announced "Testing..." immediately, followed by the testing information/result. I think it would be good to preserve that behavior when addressing this issue.

For the selected-server announcement, I would prefer a clean and explicit solution rather than relying on the previous announcement storm behavior. The desired behavior is that immediately after tapping a server, TalkBack explicitly announces the newly selected server (for example, "Selected, France") without requiring the user to move the focus away and back.

Regarding Persian, I understand that Persian/Farsi is not available in your TalkBack setup. On my device, Persian accessibility announcements are read correctly. If you ever want to test the Persian announcements yourself, eSpeak-NG can be installed as a speech engine and selected as the default TTS engine; it supports Persian and can be used to test these announcements directly.

Thanks again for taking the time to investigate these issues.

@eliotcougar

eliotcougar commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Regarding the "Testing" announcement, I should mention that this behavior was actually working very well in PR #6112. Each time I tapped the test action, TalkBack announced "Testing..." immediately, followed by the testing information/result. I think it would be good to preserve that behavior when addressing this issue.

Fixing is right now together with the cleaner cross-PR resources split adjustments.

UPD: The loss of "Testing..." announcement was the result of suppression of the bulk testing continuously updating string... Better fix is coming...

@aliRahimi1997

Copy link
Copy Markdown
Contributor

Thank you very much for your work on these PRs.

I have now tested the latest versions of PR
#6105
and PR
#6112
together in a real merge. I am happy to report that they merge cleanly without conflicts, and the combined version works correctly from my perspective.

For the accessibility changes covered by these PRs, I consider the work complete and I am satisfied with the current result.

There are still other parts of the application that would benefit from a similar accessibility review. For example, the "Check for updates" screen currently does not seem to provide a clear accessibility announcement such as "You are up to date" when no update is available. Other screens should also be reviewed to make sure that useful status messages and announcements have not been removed or are not being exposed correctly to TalkBack.

Unfortunately, I will be travelling for a while starting tomorrow and will not be able to perform further testing for some time. Therefore, I would suggest merging these PRs into the main project now, and we can investigate the remaining accessibility issues and improve other screens later when I am able to continue testing.

Thank you again for the extensive work on the accessibility improvements.

@eliotcougar
eliotcougar marked this pull request as draft August 23, 2026 12:25
@eliotcougar
eliotcougar force-pushed the fix/talkback-subscription-rows branch from 46a9b66 to 9f040af Compare August 23, 2026 12:43
@eliotcougar eliotcougar changed the title TalkBack: improve accessibility across settings screens TalkBack: consolidate app accessibility behavior Aug 23, 2026
@eliotcougar
eliotcougar force-pushed the fix/talkback-subscription-rows branch from 9f040af to ca85351 Compare August 23, 2026 13:19
@eliotcougar eliotcougar changed the title TalkBack: consolidate app accessibility behavior TalkBack: app accessibility combined implementation (temporary) Aug 24, 2026
@eliotcougar
eliotcougar force-pushed the fix/talkback-subscription-rows branch from ca85351 to ba01f0c Compare August 29, 2026 19:14
@eliotcougar

Copy link
Copy Markdown
Contributor Author

Accessibility implementation policy and current refactor status

I want to make the direction of this work explicit after the maintainer feedback. The accessibility layer in this PR will be implemented in accordance with the official Android and Jetpack Compose accessibility guidance, even where the sentence generated by a particular screen reader sounds less polished than the hand-crafted wording testers preferred.

Tester feedback remains essential: it tells us when navigation is inefficient, a label lacks context, important information is missing, or a transient result is not communicated. However, the solution must preserve the semantic contract exposed to every accessibility service. We should provide the correct label, role, state, action, enabled/disabled status, selection state, grouping, and reading order, then let TalkBack, Commentary, Switch Access, Voice Access, and other consumers decide how to present those properties.

In particular, the order in which TalkBack speaks those properties is not controlled by the app. A phrase such as “Setting name, on, switch”, with the role pronounced at the end, is normal service-generated output. Removing the Switch/Checkbox/RadioButton/Tab role, replacing the control with a generic click action, or inserting role words into contentDescription can make one TalkBack sentence sound shorter, but it discards machine-readable meaning and can make the control worse for other services. We therefore need to retain the native role even when testers do not like where TalkBack pronounces it. The same applies to platform-provided “selected”, “on/off”, “disabled”, and action hints, which may be spoken using the accessibility service's language and ordering rather than the app locale.

The current guideline-alignment refactor is in ba01f0cb.

What has already changed

  • Genuine controls are restored. Settings and per-app rows use toggleable; app rows use checkbox semantics; language choices use selectable; group tabs and server rows use selectable/selectableGroup. These expose the actual state and action instead of simulating a control with a custom description and hand-written onClick semantics.
  • Native roles and states are restored. Switch, checkbox, radio-button, tab, selected, checked, and disabled semantics are once again available to accessibility services. The drawer now uses the semantics supplied by NavigationDrawerItem instead of replacing them to suppress the spoken role.
  • Duplicate visual controls no longer create duplicate actions. Where the entire row is the logical control, the parent owns the real toggleable action and the visual Switch/Checkbox is non-interactive. Compose's built-in clickable/toggleable semantics merge the row's child content into one logical target.
  • Disabled controls no longer receive fabricated “Unavailable setting…” nodes. They remain their genuine clickable or toggleable component with enabled = false, so the platform exposes the correct disabled status, role, label, state, and absence of an available action.
  • Accessibility-focus manipulation has been removed. The delayed server-row FocusRequester path and its state plumbing are gone. Selecting or restarting a server must not be implemented by forcibly moving accessibility focus after a timeout. The remaining focus requests in ordinary text-input and dialog code are normal keyboard/default-action focus, not an attempt to drive TalkBack focus.
  • Direct announcement events have been removed. The custom TYPE_ANNOUNCEMENT dispatcher, locale-span injection, and global announcement timing logic are gone. Important foreground transient messages now opt into a short-lived LiveRegionMode.Polite node while the visible Snackbar/Toast surface remains outside accessibility navigation. Rapid or unimportant status changes do not opt in.
  • Clearing and rebuilding complete control semantics has been reduced. Settings, per-app switches, language options, tabs, server rows, connection status, and drawer items now retain the semantics supplied by Compose/Foundation/Material APIs.
  • The top-bar reading-order improvement is retained without taking focus. The title uses traversal ordering so it precedes Menu/Back during linear navigation. This is a supported ordering adjustment; it does not call requestFocus or generate an announcement when a screen opens.
  • Useful app-specific context is retained. Whole-row grouping, named Share/Edit/Delete actions, named deletion confirmations, omission of redundant URLs/package IDs, localized dates/plurals, routing context such as “routed directly/through the app”, and deliberately selected transient results remain valid accessibility work. This information supplements what the platform cannot infer from the visual layout; it must not replace the underlying role, state, or action.

What still needs to change before this is ready

  • Rebase and narrow the PR history. Compose: improve group paging and connection-test results #6105 has now merged, while this temporary branch still carries an older stacked history. The final focused accessibility PRs need to be rebuilt on current upstream so architectural work is not reviewed again as accessibility code.
  • Continue auditing the remaining custom row descriptions. Subscriptions, routing rules, and asset rows still intentionally combine visible information and omit noisy details. Each remaining clearAndSetSemantics/custom contentDescription use must be checked individually. We should prefer native merging and hideFromAccessibility for genuinely redundant/decorative children, and only replace semantics when the component is truly custom and every required property is reconstructed.
  • Avoid duplicating native state in prose. App-localized descriptions should provide domain context, not spell out “switch”, “checkbox”, “selected”, “on/off”, or interaction instructions when those are already represented by semantic properties. A custom stateDescription should be used only where the default state is genuinely insufficient.
  • Keep the live-region policy narrow and test it by flow. Service start/stop/restart, running-server changes, connection tests, subscription updates, asset updates, and errors must each expose only meaningful terminal or acknowledgement events. Frequently updating progress must not flood the live region, and notification-backed events must not be spoken twice. Background/service-only events should use the appropriate platform notification path rather than relying on a foreground Compose host.
  • Add semantic regression coverage. Tests should assert node count, role, selected/toggle state, disabled state, actions, and merged labels—not exact TalkBack sentence order, which belongs to the accessibility service. On-device TalkBack/Commentary testing remains necessary for end-to-end behavior after each focused split.
  • Split the broad implementation into reviewable phases. Once this preparatory cleanup is complete, the behavior should move into smaller focused branches/PRs so each semantic pattern can be reviewed and merged independently.

Official guidance being followed

  • Compose semantics: Foundation and Material controls provide role/function semantics; accessibility services decide how to represent them.
  • Compose API accessibility defaults: clickable and toggleable provide interaction semantics and merge their descendants into one logical entity.
  • Merging and clearing semantics: merge when children form one control; clear/replace semantics only when that information genuinely must be removed or fully reconstructed.
  • Accessibility principles: prefer built-in accessibility behavior and provide useful labels/actions without reimplementing platform functionality.
  • Accessible composables: preserve roles and standard interactions, and adjust traversal order only where the default reading order is insufficient.

This push is therefore a preparatory correction, not a claim that the broad PR is ready to merge. The commitment going forward is: fix missing meaning and inefficient navigation, but do not fight the accessibility framework merely to force one preferred spoken sentence.

@eliotcougar
eliotcougar force-pushed the fix/talkback-subscription-rows branch from ba01f0c to 17312c0 Compare August 29, 2026 19:40
@eliotcougar

Copy link
Copy Markdown
Contributor Author

I have split the transient notification accessibility layer out of this PR into draft PR #6157.

That focused PR now owns:

  • accessibility-inert visual Snackbars and fallback Toasts;
  • locale-aware polite and assertive live regions;
  • rapid duplicate suppression that preserves priority changes;
  • the transient-message opt-in call sites;
  • service-state announcement priority;
  • localized subscription/config and asset-update plurals; and
  • completed service-stop wording in every maintained locale.

This branch has been rewritten to remove that layer completely. #6112 is now limited to the remaining row, control, dialog, and screen semantics instead of carrying a second notification implementation.

Both branches are based directly on current upstream. They merge cleanly in either order, both orders produce the same resulting tree, and the exact merged result compiles and passes the full unit-test suite. Because #6112's single commit was narrowed in place, its remote branch was updated with a verified force-with-lease.

This is the first step in breaking the larger accessibility bundle into small, independently reviewable PRs while keeping the tested behavior intact.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

The accessibility work has been refactored in accordance with the official Android accessibility guidelines and split into 10 separate non-intersecting PRs.

@aliRahimi1997

Copy link
Copy Markdown
Contributor

I tested the combined accessibility changes on a real device with TalkBack enabled. Most of the changes work very well, and I found only a few issues that appear worth addressing:

  1. Main server list (TalkBack, 9 of 10: Improve main server list accessibility #6167)
    Unselected server rows are announced with their state and radio-button role, for example:

“Not selected. 🇫🇮 FI. pt-play.mobileemdad.*** : 443. VLESS / reality. Radio button.”

The selected server is announced as:

“Selected. 🇩🇪 Test 1. crm.mobileemdad.*** : 3131. VLESS / xhttp / reality. Radio button.”

The row actions themselves work correctly and I did not observe any accidental activation. However, announcing “Not selected” and “Radio button” for every unselected server is quite verbose and becomes annoying when navigating through many servers. Ideally, the normal server rows should remain as they were, while the selected server's state could be exposed if this can be done while preserving the proper accessibility semantics.

  1. Subscription list (TalkBack, 2 of 10: Improve subscription list accessibility #6158)
    The localized timestamp works correctly. For example:

“Import sub. Last updated on 31 August 2026, 03:18.”

However, removing the URL from accessibility information causes a problem when multiple subscriptions have no unique name. For example, a user can import several subscriptions and they may all appear as “Import sub”. In that situation, TalkBack gives identical information for multiple subscriptions, so the user cannot determine which subscription is which.

The URL therefore needs to remain available to accessibility services when it is necessary to distinguish subscriptions that do not have a unique/more meaningful name. The visual URL does not necessarily need to change.

  1. Connection test (TalkBack, 10 of 10: Announce connection test feedback #6168)
    The connection-test result is announced once correctly.

However, after the test completes, when navigating back to the test button, TalkBack only reads:

“Connected.”

The resulting connection status does not appear to be exposed/read as part of the button's accessible state in a sufficiently informative way. It would be useful for the final status to remain available through the accessibility semantics of the relevant UI, not only as the one-time transient announcement.

  1. Navigation drawer (TalkBack, 8 of 10: Improve shared navigation and action labels #6166)
    When opening the navigation drawer, TalkBack initially places accessibility focus on a node that speaks nothing. The user has to swipe once before reaching the first actual navigation item.

Also, the navigation items are announced with “Tab” at the end, for example:

“Settings, tab”
“Per-app proxy, tab”

The navigation items otherwise work correctly. The main issue is the silent initial accessibility-focus target and whether the “Tab” role is actually appropriate for these drawer items.

  1. Settings switches (TalkBack, 5 of 10: Improve Settings item accessibility #6161)
    The switches expose their current state correctly when navigating to them.

However, after activating a switch and changing its state, TalkBack does not immediately report the new On/Off state. The user has to navigate away and return to the switch to hear the new state.

Aside from these points, I tested the following areas without finding problems:

Overall, the accessibility improvements are working quite well. These are the remaining issues I found during real-device TalkBack testing.

@eliotcougar

Copy link
Copy Markdown
Contributor Author

@aliRahimi1997 Thank you for the detailed real-device testing. I am GPT-5.6 Sol, the AI assistant working with @eliotcougar, and I am posting this response at their request.

The rejected approach in this PR accumulated too much custom speech, focus manipulation, and replacement accessibility semantics. That was an implementation problem on our side, not a problem with your testing. The smaller replacement PRs should remove that complexity, not merely redistribute it.

There is an important distinction between fixing missing accessibility information and making a screen reader pronounce a particular sentence. Android recommends short app-provided labels, with control type and state conveyed separately through semantics; the accessibility service then presents them. Its warning against putting “button” or “checked” into a label is not a recommendation to remove the actual role or checked state. Official guidance on labels, types, and states.

Here is how that applies to your five points:

1. Server rows: “Not selected” and “Radio button” (#6167)

Your qualification about preserving proper semantics is important. Selecting one active server is a single-choice interaction. Android's documented pattern exposes the selection group, the selected state of each option, and Role.RadioButton. The role describes the interaction, not just the shape of the visible control. Android's single-selection example.

We should therefore retain the meaningful role and both selected/unselected states. Silencing those by erasing semantics or substituting a hand-written spoken sentence would trade structured information for one preferred TalkBack output. There is no app-side guarantee of the exact spoken wording while leaving the same semantics intact. TalkBack itself provides user-controlled verbosity settings.

Consequently, suppressing the correct role/unselected state solely for brevity is not a change we should make. Redundant app-provided metadata or duplicate focus stops are a different matter and remain worth simplifying. This is a design conclusion from the guidelines, not a claim that Android mandates radio-button semantics for every selectable list.

2. Indistinguishable subscriptions (#6158)

This is a valid accessibility concern, not something the guidelines justify leaving unresolved. Android explicitly calls for distinguishable labels for collection items. A URL is not redundant when it supplies otherwise missing identity. Collection labeling guidance.

The newer blank-name fallback supplies “Unnamed.” plus the URL host and uses that identifier in associated actions and confirmations. However, it does not resolve repeated nonblank names such as “Import sub”, and a host alone cannot distinguish two subscriptions on the same host. That remaining case needs meaningful disambiguation. It does not necessarily require automatically speaking an entire subscription URL, including credentials or access tokens.

3. A connection-test result must remain accessible (#6168)

Agreed. A one-time announcement is not a substitute for information the user can revisit. Updated semantic text/state and live-region notifications serve different purposes. Android's semantics guidance.

There is now a follow-up commit in #6168 retaining the completed result on the status row. That implementation still deserves real TalkBack testing; I am not treating source inspection or semantics tests as proof of its spoken behavior on your device.

4. Drawer: silent initial target versus “Tab” (#6166)

These are separate issues. The empty decorative header was a genuine problem: decorative content should not create a meaningless navigation stop. A follow-up in #6166 hides that container from accessibility without forcing focus elsewhere. Android's decorative-content guidance.

The “Tab” role, however, comes from Material 3's NavigationDrawerItem: AndroidX's implementation explicitly sets Role.Tab. It is not an extra word we added to the label. Its appropriateness can be discussed as a component-design issue, but removing the role merely to silence the word would repeat the previous workaround. We should retain the standard component behavior unless there is a demonstrated semantic mismatch, not override it just for different speech.

5. Switches not reporting the new state (#6161)

This also remains a valid report. The intended implementation is one interactive row with an observable checked state and native toggle semantics, with the child switch not creating a second action target. That is the documented Compose pattern. Missing change feedback needs investigation; the guidelines do not excuse requiring users to leave and revisit the control. We should fix the state/event problem rather than mask it with an unconditional custom announcement after every tap.

Why not restore the old announcement/focus machinery?

Android warns that clearing semantics removes information from its consumers and should be used sparingly. It does allow justified custom semantics; even its custom-toggle example restores the role and toggle state. Merging and clearing guidance. Also, TYPE_ANNOUNCEMENT was deprecated in API 36 in favor of semantic alternatives; services may ignore it. Reintroducing direct announcements or timed focus jumps solely to control speech would recreate fragile behavior and maintenance costs.

In short: we should decline speech-only suppression of correct roles/states, not decline genuine accessibility fixes. Your reports about identity, persistent results, empty focus targets, and state-change feedback remain useful and actionable. The goal is a smaller, standards-based implementation that works across accessibility services, rather than one that scripts a particular reader's output.

@eliotcougar
eliotcougar deleted the fix/talkback-subscription-rows branch September 15, 2026 17:17
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