Repository navigation
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…-Shilin#100) The Agent Mode user service always runs in user 0: Shizuku starts it as shell (uid 2000), the su fallback as root (uid 0), and Shizuku even shares one user service process between the same package in every user. Every API that acts on "the user" therefore acted on the owner user whenever Aether ran in a secondary user or a work profile: - the virtual display stayed black, because the activity was launched for user 0 while Aether's user was in the foreground. ActivityRecord.showToCurrentUser() keeps another profile's windows invisible, so SurfaceFlinger only produced black frames and the "screenshot" was a black image instead of an error; - list_apps returned the owner user's packages; - pasted text (CJK, emoji) went to the owner user's clipboard. Aether now passes its own user id (Process.myUid() / 100000) to the service, which builds a cached per-user context through the hidden createPackageContextAsUser (the "android" special case keeps this process's shell package identity and does not need the host package installed in the target user). Package resolution, the pending intent that starts the activity and the clipboard all use that context. PendingIntent.getActivity only stamps the context user on Android 9+, so older releases go through the hidden getActivityAsUser or refuse to launch instead of silently starting in the wrong user. A caller-user mismatch is logged rather than accepted, and captures now report black placeholders (screenshot_blank plus screenshot_blank_reason), so a failed launch cannot masquerade as a rendered screen. The Shizuku user service version is bumped, otherwise a cached process from an older build would keep answering the old AIDL.
- captureImageToFd derives the blank reason first and draws its black placeholder once instead of duplicating the call in both branches. - warnIfCallerUserDiffers moves into contextForUser, so every user-scoped call (launchPackage, listInstalledAppsJson and the clipboard path behind text) is guarded exactly once, and the user-scoped context builder logs its single line after either reflection path. - On Android 8.x the public PendingIntent.getActivity still stamps the process user, so keep using it while the requested user is this service process's own user (the owner-user behaviour Agent Mode had before cross-user support) and only reach for the hidden getActivityAsUser for other users. Android 9+ behaviour is unchanged: owner-user launches, list_apps, clipboard and capture semantics stay exactly as tested on device.
yumiru11
force-pushed
the
fix/agent-mode-multi-user
branch
from
October 3, 2026 14:48
0981a7d to
7b01dc7
Compare
yumiru11
marked this pull request as ready for review
October 3, 2026 14:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #100.
Root cause
Agent Mode's user service never runs in Aether's user: Shizuku starts it as shell (uid 2000), the su fallback as root (uid 0), and Shizuku deliberately shares one user service process between the same package in every user (
UserServiceArgs.tagjavadoc: "user service is shared by the same packages installed in all users"). Every API that acts on "the user" therefore acted on the owner user:list_appsand the clipboard ran on the user-0 process contexts;PendingIntentRecordstarts the activity as the pending intent's own user (key.userId), and the pending intent had been created from a user-0 display context, so the activity was started for user 0;ActivityRecord.showToCurrentUser()→isCurrentProfile(mUserId)keeps another profile's windows invisible, so the virtual display only ever composed black frames — exactly the reported "black image, not an error" — while the task was recorded under the owner user.Changes
launchPackage(..., userId),text(..., userId),listInstalledAppsJson(userId);captureImageToFdnow returns a capture status.contextForUser(userId)builds a cached per-user context through the hiddencreatePackageContextAsUser("android", …). The framework special-cases that package (it copies the current context and only replaces the user), which keeps the shell package identity that the clipboard and the virtual-display owner check rely on, and works even where the host package is not installed in the target user. Package resolution, launching and clipboard writes all go through it.getActivityAsUseris used for other users — failing loudly rather than launching in the wrong user.ShizukuUserServiceVersion2 → 3, so a cached user service from an older build is recreated instead of answering the old AIDL.screenshot_blank+screenshot_blank_reason=launch_failed/no_launched_content), so a failed launch can no longer masquerade as a rendered screen.Review
Ran the repo's two-axis review: Standards — 0 hard violations, 5 judgement calls, and the three worth acting on (duplicated blank-frame branch, missing caller/user guard on the clipboard path, API<28 regression risk) are fixed in the second commit. Spec — every point from the issue is covered (the root authorization path shares the same AIDL and now passes the same user id, the twice-resolved package now resolves in one user, and the intentional black-image branch is addressed); the remaining notes are deliberate scope decisions.
Verification
:shared:testDebugUnitTest,:app:testDebugUnitTestand:app:assembleDebugare green.Known limits
createPackageContextAsUser; a device that refuses it gets an explicit error instead of a silent owner-user fallback.source=display; only placeholders are annotated.Summary by CodeRabbit