HOME Ownership Design - aagocs/magicdesk GitHub Wiki
UX-010: Desktop HOME ownership design
Status: source-investigated proposal; device probe pending. Nothing here is device-verified. Read product direction and the UX plan first. This document separates three kinds of statement:
- Source fact — traced in this repository at the cited file.
- Framework hypothesis — expected AOSP behavior that must be confirmed on a device before any implementation depends on it.
- Proposal — the intended change, gated on the probe below.
Today: one lease owns three different things
DesktopHomeRoleLease is a single persisted record that currently carries:
- Primary HOME role (
android.app.role.HOME).activate→claimadds MagicDesk as the role holder for every Desktop, external or phone, and restores the previous holder on release. (source fact) - SECONDARY_HOME preference.
FrameworkSecondaryHomeApireplaces the user's preferredSECONDARY_HOMEhandler withDesktopActivitythroughIPackageManager.replacePreferredActivity, and restores the captured previous handler. This is a PackageManager preference, independent of the HOME role. (source fact) - Session membership.
targets,policy,compatibility,phaseandclosingDisplayIdare read by roughly twenty call sites (DesktopShellActivity,RuntimeDesktopSessionCoordinator,DesktopTaskWatcher,FullscreenStartController, automation state, diagnostics, self-test cleanup, …) as the authoritative "which Desktop sessions exist" registry.isActiveForDisplayrequiresphase == ACTIVE, which is only reached after the primary role claim succeeds. (source fact)
Component enablement couples them further: DesktopHomeSurfaceRouter enables
PhoneHomeActivity (primary HOME filter) on display 0 whenever any external
workspace exists, so the phone presents MagicDesk's launcher surface during an
external-only session. (source fact)
Phone Recents is coupled through isPhoneOverviewRoutingActive:
ShellPhoneOverviewRouter intercepts the firmware Recents component and
presents MagicDesk's phone HOME while the lease is active. (source fact)
How the external host becomes a HOME task
ShellDesktopHostLauncher launches the explicit DesktopActivity component
through TaskDisplayAreaLaunchCommand.launchFullscreenTask with
ACTIVITY_TYPE_HOME, which FrameworkActivityLaunchApi.setActivityType passes
as ActivityOptions.setLaunchActivityType. It then verifies that the resulting
task really has ACTIVITY_TYPE_HOME and fails otherwise. (source fact)
Framework hypothesis H1. The HOME activity type of the host comes from the
launch options creating a HOME root task in the target display area, not from
the caller being the role holder. Per-activity HOME classification in AOSP
ActivityRecord.setActivityType is restricted for explicit-component launches
from non-system callers, so the task-level path is the plausible mechanism.
If H1 holds, an external HOME host can be admitted while the OEM launcher keeps
the primary role.
Framework hypothesis H2 (risk). When Android itself (re)starts HOME on a
secondary display — display added, host task removed, a HOME key/gesture
targeting that display — AOSP RootWindowContainer.resolveSecondaryHomeActivity
first looks for a SECONDARY_HOME activity inside the primary HOME
package, and only falls back to the preferred SECONDARY_HOME resolution
when none exists (or when config_useSystemProvidedLauncherForSecondary is
set). Many OEM launchers ship such an activity. Therefore, with the OEM
launcher keeping the primary role, MagicDesk's SECONDARY_HOME preference may
not win framework-initiated HOME starts on the workspace display. This is the
central unknown; it decides whether "preference only" is sufficient.
Framework hypothesis H3. Recents on display 0 resolves to the system/OEM recents component when MagicDesk does not hold the primary role, so the phone overview router simply stays disabled for external-only sessions.
Proposal: split ownership by what each target needs
| Session shape | Primary HOME role | PhoneHomeActivity |
SECONDARY_HOME preference | Phone Recents routing |
|---|---|---|---|---|
| External-only (wired, wireless, portable/virtual) | Not claimed — OEM retained | Disabled | Claimed while any external workspace exists | Off |
| Explicit phone Desktop | Claimed (unchanged behavior) | Enabled | Claimed if an external workspace also exists | On |
| None | Not claimed | Disabled | Restored | Off |
Structural changes, in order:
- Extract session membership from the HOME lease into a workspace
registry with its own phase (
PREPARED/ACTIVE/RELEASING/RECOVERING). Call sites that only ask "is a Desktop active on display N" read the registry. Implemented (read side, behavior-neutral, #6):DesktopWorkspaceMembershipis now the only membership read path; the lease's ownisActiveForDisplay/isReleasingForDisplaywere removed. DirectDesktopHomeRoleLease.snapshot()readers are limited to HOME ownership, recovery and diagnostics by an allowlist guard inDesktopWorkspaceMembershipTest. Membership is still persisted inside the lease record; splitting storage moves to step 2 (#7), where the two lifetimes first diverge and crash-recovery ordering between two records can be designed against real semantics rather than a no-op split. - Make the primary role a sub-lease acquired only by a
DesktopDisplayTarget.isDefaultWorkspace()target and released when the last phone workspace closes, even if external workspaces continue. Its restoration rules (capture previous holder, honor a user's newer choice,STARTUP_RELINQUISHED) stay exactly as today. - Keep the SECONDARY_HOME preference as the workspace-wide sub-lease, restored when the last workspace closes.
- Router:
DesktopHomeSurfaceRouter.Selection.primarybecomesLAUNCHERonly when a phone workspace exists, not "any workspace exists". - Recovery:
DesktopHomeStartupGuardandreconcilerestore each sub-lease independently; a stale SECONDARY_HOME preference never causes a primary-role change.
If H2 fails (OEM secondary launcher wins framework-initiated HOME starts), the
bounded alternative inside the current task model is: keep the explicit host
launch (H1), and have the existing DesktopTaskWatcher treat an unexpected
foreign HOME task on an owned workspace display as a host-loss event handled by
the existing recovery path — not a new periodic query, and not a
force-launch of anything on display 0. Do not replace the host with an
ordinary fullscreen Activity (wallpaper, stacking and fullscreen-plane
behavior depend on HOME type; see fullscreen transitions).
Device probe protocol (for an agent with an authorized device)
Run on API 35+ under shell UID 2000 with the existing runtime and MCP; no second organizer, no root. Record pass/fail/unavailable per step and post only anonymized results. Restore everything by exact identity afterward. Do not run on a device whose user has not authorized HOME/preference changes.
| Step | Action | Observe | Answers |
|---|---|---|---|
| P0 | Record OEM role holder, SECONDARY_HOME resolution, whether the OEM package exports a SECONDARY_HOME activity, config_useSystemProvidedLauncherForSecondary |
Values only (package names of system launchers are fine; no app inventory) | H2 precondition |
| P1 | With OEM holding the role and MagicDesk's components in today's prepared state (SECONDARY_HOME enabled), create an owned virtual display and launch the host via the existing launcher path without activate/role claim |
Task created? ACTIVITY_TYPE_HOME? Wallpaper/stacking correct? |
H1 |
| P2 | Same display: remove the host task by exact id, then let Android restart HOME (or send HOME to that display) | Which component becomes HOME on the workspace display | H2 |
| P3 | Repeat P2 after claimSecondaryHome (preference only, role untouched) |
Does the preference win? | H2 |
| P4 | During P1: phone HOME key, Recents gesture, notification shade, open/close a fullscreen app on display 0 | OEM behavior unchanged? | Product invariant 1 / H3 |
| P5 | Cleanup: restore SECONDARY_HOME preference, disable components, remove display, release awake lease | Prior state byte-for-byte (role holder, preference) | Cleanup contract |
Report results on #5 with the build identity reference kept private.
Acceptance
The UX plan's mixed-residency transition table and acceptance matrix remain the
acceptance criteria. This design adds one rule: no external-only path may
call the primary-role claim. A host fixture should assert that, with fake
backends, for every external-only start/join/close/failure/recovery sequence,
setHomePackage/clearHomePackage are never invoked and the captured role
holder is untouched.