Repository navigation
feat(settings): keep-awake on and off across android and harmonyos #4290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Android keep-awake end-to-end run and the svc-true no-op, 2026-11-08 | ||
|
|
||
| Evidence for #4290 (keep-awake on and off across Android and HarmonyOS): the | ||
| first backend draft lowered `on` to `svc power stayon true`, and a live run | ||
| proved that a no-op on real hardware — the command answers and the stored | ||
| value never moves when the mask already covers the plugged-in sources. The | ||
| shipped backend writes the setting itself and reports the read-back state. | ||
|
|
||
| ## Device | ||
|
|
||
| - Xiaomi Redmi K60 (`2312DRAABC` model code), Android 14 (SDK 34), adb serial | ||
| `5p4hqglvpzxshiuw`, USB connected. | ||
|
|
||
| ## The svc-true no-op (discovered live) | ||
|
|
||
| With the user's stored mask at `15`, the draft command changed nothing while | ||
| the CLI still acked the write: | ||
|
|
||
| ```sh | ||
| $ adb -s 5p4hqglvpzxshiuw shell "settings get global stay_on_while_plugged_in" | ||
| 15 | ||
| $ adb -s 5p4hqglvpzxshiuw shell "svc power stayon true; settings get global stay_on_while_plugged_in" | ||
| 15 | ||
| $ adb -s 5p4hqglvpzxshiuw shell "svc power stayon usb; settings get global stay_on_while_plugged_in" | ||
| 2 | ||
| ``` | ||
|
|
||
| `stayon true` does not rewrite a mask that already covers the plugged-in | ||
| sources; `stayon usb` rewrites to `2`. A write that skips no-op spellings is | ||
| the only reliable form. | ||
|
|
||
| ## The shipped backend, end to end through the public CLI | ||
|
|
||
| Branch build of #4290, one command per state, device state read back with raw | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This live run is not pinned to the source revision it exercised. Record the tested commit SHA so these device results remain attributable to this implementation. Prompt for AI agents |
||
| `settings get` between the calls: | ||
|
|
||
| ```sh | ||
| $ node bin/agent-device.mjs settings keep-awake on --platform android --serial 5p4hqglvpzxshiuw | ||
| Updated setting: keep-awake | ||
| $ adb -s 5p4hqglvpzxshiuw shell "settings get global stay_on_while_plugged_in" | ||
| 7 | ||
| $ node bin/agent-device.mjs settings keep-awake off --platform android --serial 5p4hqglvpzxshiuw | ||
| Updated setting: keep-awake | ||
| $ adb -s 5p4hqglvpzxshiuw shell "settings get global stay_on_while_plugged_in" | ||
| 0 | ||
| ``` | ||
|
|
||
| `15 → 7 → 0`. The user's stored mask was restored to `15` after the run. The | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The transcript ends at Prompt for AI agents |
||
| CLI result carries the read-back state (`stayOnWhilePluggedIn`), so an ack can | ||
| never claim a write the device did not store. | ||
|
|
||
| ## HarmonyOS note | ||
|
|
||
| `power-shell` on JDY-AL50 (7.0.0.111) has no query form: `setmode` spells only | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The HarmonyOS section does not provide a live-run transcript for the JDY-AL50 claim. Add the exact command output and resulting state (including Prompt for AI agents |
||
| the 600-series modes, `timeout -o <ms>` acks with `Override screen off time to | ||
| 2147483647`, and `timeout -r` restores. The harmony backend uses the timeout | ||
| override for exactly this reason. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -652,3 +652,42 @@ test('setAndroidSetting mobile-data stops sampling when the request is cancelled | |
| }, | ||
| ); | ||
| }); | ||
|
|
||
| // --- settings keep-awake: explicit stay-on mask write --- | ||
|
|
||
| test('setAndroidSetting keep-awake writes the mask and reports the read-back state', async () => { | ||
| for (const { state, value, keepAwake } of [ | ||
| { state: 'on', value: '7', keepAwake: 'enabled' }, | ||
| { state: 'off', value: '0', keepAwake: 'disabled' }, | ||
| ] as const) { | ||
| await withFakeAdb( | ||
| (args) => { | ||
| const joined = args.join(' '); | ||
| if (joined === `shell settings put global stay_on_while_plugged_in ${value}`) return ''; | ||
| if (joined === 'shell settings get global stay_on_while_plugged_in') return value; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This fixture always reports the requested mask from Prompt for AI agents |
||
| return undefined; | ||
| }, | ||
| async ({ calls, device }) => { | ||
| const result = await setAndroidSetting(device, 'keep-awake', state); | ||
| assert.deepEqual(flatCalls(calls), [ | ||
| `shell settings put global stay_on_while_plugged_in ${value}`, | ||
| 'shell settings get global stay_on_while_plugged_in', | ||
| ]); | ||
| assert.deepEqual(result, { keepAwake, stayOnWhilePluggedIn: value }); | ||
| }, | ||
| ); | ||
| } | ||
| }); | ||
|
|
||
| test('setAndroidSetting keep-awake runs nothing for a state svc cannot spell', async () => { | ||
| await withFakeAdb( | ||
| (args) => ({ stderr: `unexpected args: ${args.join(' ')}`, exitCode: 1 }), | ||
| async ({ calls, device }) => { | ||
| await assertRejectsAppError(() => setAndroidSetting(device, 'keep-awake', 'forever'), { | ||
| code: 'INVALID_ARGS', | ||
| message: /Invalid setting state: forever/, | ||
| }); | ||
| assert.deepEqual(calls, []); | ||
| }, | ||
| ); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,6 +40,7 @@ export const ANDROID_SETTINGS = [ | |||||||||||||||||||||||||||||||||||||||||||||
| 'bluetooth', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'airplane', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'mobile-data', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'keep-awake', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'location', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'animations', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'appearance', | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -114,6 +115,30 @@ export async function setAndroidSetting( | |||||||||||||||||||||||||||||||||||||||||||||
| signal, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| case 'keep-awake': { | ||||||||||||||||||||||||||||||||||||||||||||||
| // `svc power stayon true` is a no-op when the stored mask already covers the plugged-in | ||||||||||||||||||||||||||||||||||||||||||||||
| // sources (a device holding 15 answers "Updated" with the value unchanged — verified on | ||||||||||||||||||||||||||||||||||||||||||||||
| // a Redmi K60, Android 14). The write goes to the setting itself with the explicit | ||||||||||||||||||||||||||||||||||||||||||||||
| // BATTERY_PLUGGED_ANY mask, and the result carries the read-back state. | ||||||||||||||||||||||||||||||||||||||||||||||
| const enabled = parseSettingState(state); | ||||||||||||||||||||||||||||||||||||||||||||||
| await runAndroidShell(device, [ | ||||||||||||||||||||||||||||||||||||||||||||||
| 'settings', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'put', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'global', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'stay_on_while_plugged_in', | ||||||||||||||||||||||||||||||||||||||||||||||
| enabled ? '7' : '0', | ||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||
| const applied = await runAndroidShell(device, [ | ||||||||||||||||||||||||||||||||||||||||||||||
| 'settings', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'get', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'global', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'stay_on_while_plugged_in', | ||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||
| keepAwake: enabled ? 'enabled' : 'disabled', | ||||||||||||||||||||||||||||||||||||||||||||||
| stayOnWhilePluggedIn: applied.stdout.trim(), | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+137
to
+140
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The Android result can report Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| case 'location': { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (state.toLowerCase() === 'set') { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (device.kind !== 'emulator') { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,28 @@ test('HarmonyOS clear-app-state force stops then clears bundle data and cache', | |||||||||||||
| ); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| test('HarmonyOS keep-awake raises the screen-off timeout and off restores the system value', async () => { | ||||||||||||||
| assert.deepEqual(await setHarmonySetting(device, 'keep-awake', 'on'), { keepAwake: 'enabled' }); | ||||||||||||||
| assert.deepEqual(await setHarmonySetting(device, 'keep-awake', 'OFF'), { | ||||||||||||||
| keepAwake: 'disabled', | ||||||||||||||
| }); | ||||||||||||||
| assert.deepEqual( | ||||||||||||||
| mockRunHarmonyShell.mock.calls.map((call) => call[1]), | ||||||||||||||
| [ | ||||||||||||||
| ['power-shell', 'timeout', '-o', '2147483647'], | ||||||||||||||
| ['power-shell', 'timeout', '-r'], | ||||||||||||||
| ], | ||||||||||||||
| ); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| test('HarmonyOS keep-awake runs nothing for a state the tool cannot spell', async () => { | ||||||||||||||
| await assert.rejects( | ||||||||||||||
| async () => await setHarmonySetting(device, 'keep-awake', 'always'), | ||||||||||||||
| /Invalid setting state: always/, | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Assert the typed error code here, not only its message, so this test proves invalid states are rejected through the Prompt for AI agents
Suggested change
|
||||||||||||||
| ); | ||||||||||||||
| assert.deepEqual(mockRunHarmonyShell.mock.calls, []); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| test('HarmonyOS settings rejects unsupported setting and missing app bundle', async () => { | ||||||||||||||
| await assert.rejects(async () => await setHarmonySetting(device, 'wifi', 'on'), /not supported/); | ||||||||||||||
| await assert.rejects( | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The transcript does not substantiate the claimed CLI acknowledgement; it only runs raw ADB commands. Include the draft CLI invocation and its acknowledgement output alongside the device read-back.
Prompt for AI agents