You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(android): fold a foldable emulator through the console posture - #3361
agent-device fold closed|half-open|open now works on foldable Android emulators. Until now the Android owner refused it ("the Android emulator posture control is not driven by agent-device yet").
How it works
adb emu posture 1|2|3 moves the hinge to the angle the AVD profile defines for the posture (0°/90°/180° on the Pixel folds, 15°/90°/165° on the generic "7.6in Foldable"), so half-open is 90° here (the Duo's is 130°).
The pose is verified by polling cmd device_state print-state until the guest commits CLOSED, HALF_OPENED, or OPENED, looked up by name because profiles number the states differently (Pixel folds from 0, the generic foldables from 1). That committed state is the verification; the hinge sensor is read back for hingeAngleDegrees as information.
Folding to the cover display raises "Swipe up to continue" on Pixel images (the continue-on-fold setting is ignored by emulator images), so a fold to closed dismisses a lock screen it raised; otherwise every later snapshot reads the lock screen instead of the app. Unfolding raises none, and a keyguard already showing before the fold is left alone.
A phone profile is refused as single-panel-device, a profile missing the requested posture state as fold-posture-unsupported, a physical device by kind, and keyframes with fold-keyframes-unsupported. There is no screen report: Android reports the app window on the next snapshot.
Help, MCP metadata, the commands page, and ADR 0025 are updated. 11 files changed, 645 insertions(+), 25 deletions(-)
Verified
pnpm check:affected --run on 71202f8 (the pushed head): pass, including test:integration:node.
Live on a Pixel 10 Pro Fold AVD (API 36, google_apis, arm64), app session open, each pose read back with adb afterwards:
The app keeps the window focus across the folds (mCurrentFocus=…arrangementview.example.MainActivity). A raw adb emu posture 1 on the same AVD leaves isKeyguardShowing=true; agent-device fold closed leaves it false.
Pixel 10 Pro AVD (phone): UNSUPPORTED_OPERATION, reason: single-panel-device, nothing sent to the console.
All three poses also verified on the generic "7.6in Foldable" profile (states numbered from 1) on API 35 and API 36, and on the Pixel 10 Pro Fold on API 37.1, with the raw print-states/print-state output: see this comment thread.
`fold closed|half-open|open` now poses a foldable Android emulator. The
emulator console's `posture <id>` moves the hinge sensor to the posture's
angle (0, 90, 180 degrees); the pose is verified by polling `cmd
device_state print-state` until the guest commits the posture's device
state, looked up by name since profiles number the states differently,
and the hinge sensor is read back for the reported angle. A fold to the
cover display can raise Android's "Swipe up to continue" lock screen on
Pixel images; it is dismissed so the app stays reachable. A phone profile
is refused as `single-panel-device`, a physical device by kind, and
keyframes with `fold-keyframes-unsupported`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lock-screen dismissal runs after every requested pose and does not establish whether the keyguard was already showing before the fold. Consequently, fold open or fold half-open can unlock an unrelated pre-existing unsecured keyguard, even though the documented side effect is specifically to clear the keyguard introduced by folding to the cover display. Record the pre-dispatch keyguard state and only dismiss a newly introduced lock screen (with corresponding tests).
Validate Android half-open sensor angle against 90°
The final sensor check still uses the cross-platform category classifier, so any angle from 2° through 178° is accepted for Android half-open. That can return success while the fixed 90° emulator posture is still moving or the sensor disagrees, despite this implementation and its public docs defining Android half-open as 90°. Compare against the posture’s expected fixed angle (with a small tolerance), and add a case where HALF_OPENED reports a non-90° sensor value.
Include Android foldable emulators in ADR scope
docs/adr/0025-foldable-apple-panels.md:7
The amended status says Android emulator pose support was added, but the following scope sentence still limits the ADR to iPhone Duo and Apple devices. Include foldable Android emulators in the stated coverage so the status agrees with the new amendment.
Avoid promising panel switches for every pose change
src/commands/system/index.ts:241
The description now covers Android but still states that every pose change moves the app to a different panel and point size. Android intentionally has no panel report, and transitions such as open to half-open need not switch panels. Keep the stale-ref guidance, but describe the viewport as potentially changing rather than promising a panel switch.
Clarify fold simulator support applies to iOS
website/docs/docs/commands.md:123
This Android support contradicts the immediately preceding public bullet, which still says unqualifiedly that fold is simulator-only and requires Xcode. Qualify that existing bullet as the iOS behavior so readers are not told both that Android emulators are and are not supported.
Pass the scope signal to the settle sleeps, give the unreadable hinge
angle its own hint, scope the iOS-only wording in the fold description
and the commands page, and cover the synthetic simulator kind and the
exact bound signal in the tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The keyguard can land after the device state commits; the first read was
immediate and the loop returned 250 ms later, so a late keyguard could be
missed. Every read now waits a poll first and the keyguard must stay away
for three consecutive reads.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This constant is exported only so the test can read the implementation's retry count, which expands the production module surface for test convenience. Repository testing guidance explicitly forbids test-only production exports (docs/agents/testing.md:113-114). Keep this constant private and have the test verify the bounded failure through observable calls without importing the implementation constant.
Verify the hinge sensor against the posture's own angle within 0.5°
instead of the cross-platform category, so a half-open that reads 120°
is refused. Read the keyguard before the fold and dismiss only a lock
screen the fold raised. Keep the settle budget private and prove the
bound through the observed reads. Scope the public description and the
ADR's coverage sentence to what Android actually does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The code in e80139e looks correct to me, and the 16 checks are green, but I would like live proof of the generic foldable profile before merge. The Pixel 10 Pro Fold run and the phone refusal come from the PR body, and I did not rerun them. The by-name lookup for the generic "7.6in Foldable" profile (states numbered from 1) is covered only by a hand-written print-states fixture. Can you run fold closed, fold half-open and fold open on that profile on a live emulator, and show that the committed state id from the print-states output matches the requested posture? Please also say which API levels you ran, since the assumption that the first integer in that output is the committed state id is checked only on the API 36 run and the fixture.
Not blocking: dismissFoldLockScreen runs for every pose when no keyguard showed before the fold, so open and half-open always pay at least 3 x 250 ms and 3 dumpsys window reads, and it could run only for closed (or keep the current behavior with a comment if unfolding can raise the keyguard too); the single-panel refusal at pose.ts#L157 keys on the requested state name, so a profile that lists CLOSED and OPENED but not HALF_OPENED would refuse fold half-open as single-panel, and deciding it from "none of the three listed" with its own reason for a missing posture would match the hint and docs; and the ADR at 0025-foldable-apple-panels.md now also owns the Android amendment, so a later retitle is optional. You can take or leave all of these.
I looked for a smaller design and found none. The change adds one leaf owner under packages/platform-android/src/foldable/ that mirrors the Apple foldable module through the existing setFoldPose fact and whenAdmitted seam, with no new contract, daemon route or CLI flag. Is there an existing Android keyguard or adb emu console helper I missed that could replace the two short polling loops? I found none, and the only reduction I see is scoping the keyguard settle to closed.
Nothing else stops this from merging once the live run above is posted.
…posture
Unfolding lights the inner display and raises no keyguard, so only a
fold to closed reads and settles it. A profile that lists some posture
states but not the requested one is refused as fold-posture-unsupported
naming what it lists; single-panel-device is kept for a profile that
lists none.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Thanks for 7c60ef4. The lock screen settle is now limited to closed, and a profile without a posture now gets its own reason. Both match my earlier notes. I have not reviewed the new code in detail yet.
The live run I asked for at e80139e is still missing. Can you run fold closed, fold half-open and fold open on the generic "7.6in Foldable" profile on a live emulator? Please show that the committed state id in the print-state output matches each requested posture, and say which API levels you used. This commit changes the pose path, so please also re-run the Pixel 10 Pro Fold check on this head.
I will review it again once those results are posted.
The emulator console parks the hinge at the angle the AVD profile defines
for the posture, not a fixed one: the Pixel folds use 0, 90 and 180
degrees, the generic "7.6in Foldable" 15, 90 and 165, the midpoints of
its posture ranges. Judging the sensor against 0/90/180 refused closed
and open on that profile in a live run, so the committed device state,
which WindowManager's FoldingFeature derives from, is now the sole
verification and the angle is reported as read.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This read assumes a nonzero ADB result will throw, but managed scoped ADB can return failed results instead (packages/platform-android/src/__tests__/test-utils/fake-adb.ts:31-33). A print-states failure is therefore parsed as an empty map and incorrectly reported as single-panel-device. Check exitCode and throw androidAdbResultError before parsing stdout, as the display probe does in input-actions.ts:151-160.
This issue also appears in the following locations of the same file:
Thanks, the live run on the generic profile was worth asking for: it caught a real defect, now fixed in a927301.
What the generic profile showed
The console parks the hinge at the angle the AVD profile defines for the posture, not a fixed one: the Pixel folds use 0°/90°/180°, the generic "7.6in Foldable" uses 15°/90°/165° (the midpoints of its 0-30, 30-150, 150-180 posture ranges). The sensor check I'd added in the last review round (and the category classifier before it) refused closed and open there. The committed device state, which WindowManager's FoldingFeature derives from, was right in every case, so it is now the sole verification and the angle is reported as the sensor reads it. Help, docs and the ADR say so.
Live proof on a927301 (its build), agent-device fold with a Settings session, then read back with adb
7.6in Foldable, API 35 (Android 15), system-images;android-35;google_apis;arm64-v8a
isKeyguardShowing=false after every fold on all three. print-state printed the bare id on all three API levels (35, 36, 37.1), so "first integer is the committed id" held on each; the PR body's earlier runs were API 36 (google_apis) for the Pixel 10 Pro Fold and the phone refusal.
The optional points
Keyguard settle only for closed: taken (7c60ef4). On the API 36 Pixel image, which is the one that raises the lock screen, raw console postures gave closed → true, half-open → false, open → false three times over, including half-open and open entered from closed, so unfolding never raises it. half-open and open no longer read dumpsys window at all.
Missing posture vs single-panel: taken (7c60ef4). A profile that lists none of the three states is single-panel-device; one that lists some but not the requested one is fold-posture-unsupported naming what it lists.
ADR retitle: left as is.
A smaller design: I found no existing keyguard or adb emu console helper either; the Android package only touches the console ad hoc (emu geo fix, emu finger touch, emu avd name) and nothing reads the keyguard.
An executor may hand back a failed result instead of throwing (the
managed scoped transport does), so a failed `print-states` parsed as an
empty state list and was reported as single-panel-device. Every shell and
console read now runs with allowFailure and throws androidAdbResultError
on a nonzero exit before stdout is trusted.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This equality locks in the extra trailing sleep: with the intended “every read after the first is preceded by a poll” order, N reads require N−1 sleeps. As written, the test masks the production loop’s unobserved final interval.
Final poll interval ends without a verification read
After the 60th failed read, this sleeps for one final poll interval and exits without reading again. A guest that commits during that last 250 ms is reported as fold-pose-unverified even though it met the stated 15-second settle budget. Poll before each retry so the final interval ends with an observation.
Hint exposes Android states instead of valid fold pose arguments
listed contains Android state names, but this hint presents them as valid fold pose arguments. For the covered end-stop profile it recommends OPENED, which parseFoldPose rejects; translate the states back to closed, half-open, and open.
Poll before each retry so a guest that commits in the last interval is
still seen, translate a profile's device states back to fold poses in
the missing-posture hint, and lock in that the sensor angle is reported
as read with an angle no profile would give for closed.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This follow-up review of 71202f8 finds no problems in the code, and the earlier open items are now resolved. The live run on the generic profile satisfies the evidence we asked for. That run was on a927301, not on 71202f8. From reading the code, I judge that the later exit-code wrappers and the sleep reorder do not change the success route, but I did not re-run the device on this commit and ran no local tests. All 16 checks pass at 71202f8, and there are no conflicts. The cubic-dev-ai P2 thread on the pose test (#3361 (comment)) is fixed at this head: the "reports the sensor angle as read" test now feeds 77° for a committed closed state and expects 77 back, so it does not check the angle against the pose. Please resolve that thread. Nothing else must happen before merge, so this is ready for maintainer merge.
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
ready-for-humanValid work that needs human implementation, judgment, or maintainer merge
3 participants
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.
agent-device fold closed|half-open|opennow works on foldable Android emulators. Until now the Android owner refused it ("the Android emulator posture control is not driven by agent-device yet").How it works
adb emu posture 1|2|3moves the hinge to the angle the AVD profile defines for the posture (0°/90°/180° on the Pixel folds, 15°/90°/165° on the generic "7.6in Foldable"), sohalf-openis 90° here (the Duo's is 130°).cmd device_state print-stateuntil the guest commitsCLOSED,HALF_OPENED, orOPENED, looked up by name because profiles number the states differently (Pixel folds from 0, the generic foldables from 1). That committed state is the verification; the hinge sensor is read back forhingeAngleDegreesas information.closeddismisses a lock screen it raised; otherwise every later snapshot reads the lock screen instead of the app. Unfolding raises none, and a keyguard already showing before the fold is left alone.single-panel-device, a profile missing the requested posture state asfold-posture-unsupported, a physical device by kind, and keyframes withfold-keyframes-unsupported. There is noscreenreport: Android reports the app window on the next snapshot.Help, MCP metadata, the commands page, and ADR 0025 are updated. 11 files changed, 645 insertions(+), 25 deletions(-)
Verified
pnpm check:affected --runon 71202f8 (the pushed head): pass, includingtest:integration:node.Live on a Pixel 10 Pro Fold AVD (API 36, google_apis, arm64), app session open, each pose read back with adb afterwards:
The app keeps the window focus across the folds (
mCurrentFocus=…arrangementview.example.MainActivity). A rawadb emu posture 1on the same AVD leavesisKeyguardShowing=true;agent-device fold closedleaves itfalse.Pixel 10 Pro AVD (phone):
UNSUPPORTED_OPERATION,reason: single-panel-device, nothing sent to the console.All three poses also verified on the generic "7.6in Foldable" profile (states numbered from 1) on API 35 and API 36, and on the Pixel 10 Pro Fold on API 37.1, with the raw
print-states/print-stateoutput: see this comment thread.The same driver (console posture, device-state readback by name, lock-screen dismissal) has been running on GitHub Actions for a React Native foldable library: Add e2e tests for the Features demo on iOS and Android thiagobrez/react-native-arrangement-view#22, as a yarn patch to
@e2e-dev/mobile(e2e refuses Android before reaching agent-device, so it was patched there), on a Pixel 9 Pro Fold emulator: passing run.Not done here: keyframes on Android, and a
screenreport. Both can follow if wanted.🤖 Generated with Claude Code