Skip to content

android: honor AdminConsole and PreferencesMenu MDM policies - #877

Open
Sarcouy wants to merge 1 commit into
tailscale:mainfrom
Sarcouy:mdm-visibility-keys-upstream
Open

Sarcouy wants to merge 1 commit into
tailscale:mainfrom
Sarcouy:mdm-visibility-keys-upstream

Conversation

@Sarcouy

@Sarcouy Sarcouy commented Sep 18, 2026

Copy link
Copy Markdown

TL;DR

AdminConsole and PreferencesMenu already exist in util/syspolicy/pkey but were never read by
the Android client. This wires them up. No new key names, defaults unchanged.

The problem

On a fully managed Android device — Device Owner, always-on VPN with lockdown, restrictions pushed
through setApplicationRestrictions — an administrator has no way to hide the settings screen or
the admin console link.

Both keys are declared in tailscale.com/util/syspolicy/pkey, in the block described there as
"keys with a string value that controls visibility", enforced by each platform's UI code, and both
are documented in KB 1315 — but only for Windows. On Android they were not exposed in
MDMSettings, so they were never advertised to MDM providers through app_restrictions.xml, and
nothing in the UI honored them.

Same shape as #648, which defined OnboardingFlow on Android.

What the code does

  • MDMSettings: two ShowHideMDMSetting values. allSettings reflects over
    declaredMemberProperties, so they are registered — and appear in the MDM debug view — with no
    further wiring.
  • app_restrictions.xml, strings.xml: the two restrictions, reusing the existing show_hide
    arrays so MDM providers discover them automatically.
  • SettingsView: the admin console link is gated on AdminConsole.
  • MainView: the settings entry point is gated on PreferencesMenu. When hidden, the avatar is
    still rendered as an identity indicator but is no longer a navigation target (action = null).
  • MainActivity: the settings route is gated as well, not only its entry point, because the
    route is reachable through the tailscale://navigate/settings deep link handled by
    DeepLinkNavigator. It is gated rather than removed from the graph, since a dozen sub-screens use
    backTo("settings") as their back destination; gating it there also closes the screen if an
    administrator applies the policy while it is open.
  • Avatar: focusable() and clickable() are now gated on action != null. They were applied
    unconditionally, so the avatar that MainView now passes action = null still took D-pad focus
    and showed a ripple while doing nothing. This also drops a phantom focus stop in UserView,
    whose avatar never had an action: there the ListItem itself carries the clickable when the row
    is interactive.

Both keys default to ShowHide.Show, so behavior is unchanged wherever they are not set.

What the gate covers

PreferencesMenu: hide gates the settings route and its entry point, not the sub-screens
reachable from it (userSwitcher, splitTunneling, dnsSettings, …). Those are sibling NavHost
destinations, and a NavHost composes only the current back-stack entry, so a user already inside
one when the policy arrives is not pushed back out. Entering them still requires passing through the
gated settings route.

That matches how the existing ShowHide keys behave on Android — ExitNodesPicker,
ManageTailnetLock and RunExitNode each gate their entry point and leave their route reachable —
so this change keeps the convention rather than diverging for a single key. Whether these keys
should gate routes rather than only entry points is a cross-platform question, raised in
tailscale/tailscale#21381 along with the PreferencesMenu mapping and the two screens that still
have no policy of their own.

Updates tailscale/tailscale#21381

🤖 Generated with Claude Code

Both keys are declared in util/syspolicy/pkey, in the block described
there as keys whose string value controls visibility, and both are
documented in KB 1315 — but only for Windows. On Android they were not
exposed in MDMSettings, so app_restrictions.xml never advertised them to
MDM providers and nothing in the UI honored them.

AdminConsole shows or hides the admin console link in the settings
screen. PreferencesMenu shows or hides the settings screen itself, along
with its entry point in the main view.

The settings route is gated as well, not only its entry point: it is
reachable through the tailscale://navigate/settings deep link handled by
DeepLinkNavigator. It is gated rather than removed from the graph, since
a dozen sub-screens use backTo("settings") as their back destination;
gating it there also closes the screen if an administrator applies the
policy while it is open.

Avatar applied focusable() and clickable() unconditionally, so the
avatar that MainView now passes action = null still took D-pad focus and
showed a ripple while doing nothing. Both modifiers are gated on
action != null, which also drops a phantom focus stop in UserView, whose
avatar never had an action: there the ListItem itself carries the
clickable when the row is interactive.

Both keys default to show, so behavior is unchanged when they are not
set.

Updates tailscale/tailscale#21381

🤖 Généré avec Claude Code

Signed-off-by: Sarcouy <sarcouy@protonmail.com>
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.

1 participant