Repository navigation
feat(settings): add Android mobile-data on|off - #3636
mikolajadamowicz wants to merge 4 commits into
Conversation
`settings mobile-data on|off` switches mobile data for the default data SIM on adb-backed Android targets through `svc data enable|disable`. It then reads the stored switch back until the value matches the request, within a 2 s budget, and reports `mobileData`. On one SIM the switch is read from the global `mobile_data` row. On a multi-SIM device it is read from `mobile_data<subId>` for the default data subscription (`multi_sim_data_call`). A switch that never settles fails with COMMAND_FAILED instead of reporting a change that did not happen. QA automation can use it to move a streaming session from Wi-Fi to cellular and back, and to check how the app warns about streaming on mobile data. On iOS, tvOS, and visionOS, the daemon refuses the setting before admission with UNSUPPORTED_OPERATION (reason setting-unsupported-on-leaf), because agent-device has no mobile-data control for Apple targets and simulators have no cellular radio.
…very non-Android target Mobile-data read-back: - Choose the stored row before `svc data` runs, from the active modem count (`persist.radio.multisim.config`), never from which rows exist. A stale `mobile_data<subId>` row on a single-modem device, or a seeded plain row on a multi-modem device, no longer decides the result. - Each sample reads one row. - Sample through absent or unreadable values, using a `Deadline` and the request's abort signal. - Report `mobile-data-not-applied` or `mobile-data-unreadable` as typed reasons, with `settingKey` and `storedValue` in the details. - A failure before `svc data` discloses `dispatched: no`; every failure after it discloses `unknown`. Refusals: - The daemon refuses mobile-data on every non-Android target before admission, through one shared leaf-refusal response, so a HarmonyOS or Linux session keeps its ref frame. - The Apple and Limrun iOS owners throw the same typed refusal. - `--app` is forwarded and refused with `setting_app_not_consumed`, because the switch is device-wide. - The macOS unsupported list now includes mobile-data. - Help and the commands and Limrun docs describe the refusal accurately for physical iPhones.
There was a problem hiding this comment.
8 issues found across 19 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/platform-apple/src/core/app-settings.ts">
<violation number="1" location="packages/platform-apple/src/core/app-settings.ts:66">
P2: macOS exits through the earlier `isMacOs` branch, so this guard never runs there and `mobile-data` gets the generic `INVALID_ARGS` response instead of the shared unsupported-operation refusal. Move the mobile-data guard ahead of the macOS branch.</violation>
</file>
<file name="packages/daemon/src/execution/handlers/__tests__/snapshot-settings-handler.test.ts">
<violation number="1" location="packages/daemon/src/execution/handlers/__tests__/snapshot-settings-handler.test.ts:411">
P3: This list omits the supported `vega` and `web` platforms, so the test does not verify the pre-admission refusal for every non-Android target as its name claims. Add representative devices for both platforms.</violation>
</file>
<file name="packages/platform-apple/src/core/__tests__/app-settings.test.ts">
<violation number="1" location="packages/platform-apple/src/core/__tests__/app-settings.test.ts:193">
P2: This test asserts `dispatched: 'no'` but discards the fake tool's invocation log, so a command could run and be swallowed before the same refusal is returned. Capture `calls` and assert it is empty.
(Based on your team's feedback about typed interaction refusals.) .</violation>
</file>
<file name="packages/contracts/src/settings.ts">
<violation number="1" location="packages/contracts/src/settings.ts:227">
P2: This classifies every state as device-level, so `settings mobile-data cellular --app X` is rejected as `setting_app_not_consumed` before Android can return its invalid-state error. Gate this on `readSettingState(state)` as `locationAppScope` does.</violation>
</file>
<file name="website/docs/docs/commands.md">
<violation number="1" location="website/docs/docs/commands.md:670">
P2: `mobile-data-unreadable` is based on the final sample, not whether the row was unreadable throughout polling; a row that previously read `0` can later become absent and produce this reason. Describe it as applying when the final read is not `0` or `1`.</violation>
<violation number="2" location="website/docs/docs/commands.md:672">
P3: Qualify the `setting_app_not_consumed` statement to adb-backed Android targets. Non-Android `mobile-data --app` requests hit the unsupported-target refusal before the app-scope check and return a different reason.</violation>
</file>
<file name="packages/platform-android/src/settings.ts">
<violation number="1" location="packages/platform-android/src/settings.ts:328">
P1: The abort signal is checked only after `svc data` runs, so cancellation can still switch mobile data. Check before dispatch and pass the signal through every ADB leg, including `svc data`, row resolution, and reads.</violation>
<violation number="2" location="packages/platform-android/src/settings.ts:367">
P2: The settle deadline is checked only after this ADB read, which has no timeout; the normal executor can wait 15 seconds retrying an offline device. Bound each read with the remaining deadline and pass cancellation through.</violation>
</file>
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
View guided diff | Re-trigger cubic
| resolveAndroidMobileDataSettingKey(device), | ||
| ); | ||
| return await withDispatchDisclosure('unknown', async () => { | ||
| await runAndroidShell(device, ['svc', 'data', enabled ? 'enable' : 'disable']); |
There was a problem hiding this comment.
P1: The abort signal is checked only after svc data runs, so cancellation can still switch mobile data. Check before dispatch and pass the signal through every ADB leg, including svc data, row resolution, and reads.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-android/src/settings.ts, line 328:
<comment>The abort signal is checked only after `svc data` runs, so cancellation can still switch mobile data. Check before dispatch and pass the signal through every ADB leg, including `svc data`, row resolution, and reads.</comment>
<file context>
@@ -285,6 +294,130 @@ function nearestTextSizeCategory(scale: number): (typeof TEXT_SIZE_CATEGORIES)[n
+ resolveAndroidMobileDataSettingKey(device),
+ );
+ return await withDispatchDisclosure('unknown', async () => {
+ await runAndroidShell(device, ['svc', 'data', enabled ? 'enable' : 'disable']);
+ return { mobileData: await awaitAndroidMobileData(device, settingKey, requested, signal) };
+ });
</file context>
| throw new AppError('INVALID_ARGS', getUnsupportedMacOsSettingMessage(setting)); | ||
| } | ||
| const normalized = setting.toLowerCase(); | ||
| if (normalized === 'mobile-data') throw mobileDataUnsupportedError(device.id); |
There was a problem hiding this comment.
P2: macOS exits through the earlier isMacOs branch, so this guard never runs there and mobile-data gets the generic INVALID_ARGS response instead of the shared unsupported-operation refusal. Move the mobile-data guard ahead of the macOS branch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-apple/src/core/app-settings.ts, line 66:
<comment>macOS exits through the earlier `isMacOs` branch, so this guard never runs there and `mobile-data` gets the generic `INVALID_ARGS` response instead of the shared unsupported-operation refusal. Move the mobile-data guard ahead of the macOS branch.</comment>
<file context>
@@ -61,9 +62,10 @@ export async function setIosSetting(
throw new AppError('INVALID_ARGS', getUnsupportedMacOsSettingMessage(setting));
}
+ const normalized = setting.toLowerCase();
+ if (normalized === 'mobile-data') throw mobileDataUnsupportedError(device.id);
requireSimulatorDevice(device, 'settings');
await ensureBootedSimulator(device);
</file context>
| await withFakeAppleTool(unexpectedArgs, async () => { | ||
| for (const device of [IOS_TEST_SIMULATOR, TVOS_TEST_SIMULATOR]) { | ||
| await assertRejectsAppError(() => setIosSetting(device, 'mobile-data', 'off'), { | ||
| code: 'UNSUPPORTED_OPERATION', | ||
| reason: 'setting-unsupported-on-leaf', | ||
| dispatched: 'no', | ||
| }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
P2: This test asserts dispatched: 'no' but discards the fake tool's invocation log, so a command could run and be swallowed before the same refusal is returned. Capture calls and assert it is empty.
(Based on your team's feedback about typed interaction refusals.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-apple/src/core/__tests__/app-settings.test.ts, line 193:
<comment>This test asserts `dispatched: 'no'` but discards the fake tool's invocation log, so a command could run and be swallowed before the same refusal is returned. Capture `calls` and assert it is empty.
(Based on your team's feedback about typed interaction refusals.) .</comment>
<file context>
@@ -189,6 +189,18 @@ test('setIosSetting biometric refuses an Apple TV simulator before posting anyth
});
+test('setIosSetting mobile-data refuses every Apple leaf before spawning anything', async () => {
+ await withFakeAppleTool(unexpectedArgs, async () => {
+ for (const device of [IOS_TEST_SIMULATOR, TVOS_TEST_SIMULATOR]) {
+ await assertRejectsAppError(() => setIosSetting(device, 'mobile-data', 'off'), {
</file context>
| await withFakeAppleTool(unexpectedArgs, async () => { | |
| for (const device of [IOS_TEST_SIMULATOR, TVOS_TEST_SIMULATOR]) { | |
| await assertRejectsAppError(() => setIosSetting(device, 'mobile-data', 'off'), { | |
| code: 'UNSUPPORTED_OPERATION', | |
| reason: 'setting-unsupported-on-leaf', | |
| dispatched: 'no', | |
| }); | |
| } | |
| }); | |
| await withFakeAppleTool(unexpectedArgs, async ({ calls }) => { | |
| for (const device of [IOS_TEST_SIMULATOR, TVOS_TEST_SIMULATOR]) { | |
| await assertRejectsAppError(() => setIosSetting(device, 'mobile-data', 'off'), { | |
| code: 'UNSUPPORTED_OPERATION', | |
| reason: 'setting-unsupported-on-leaf', | |
| dispatched: 'no', | |
| }); | |
| } | |
| assert.deepEqual(calls, []); | |
| }); |
| return family === 'macos-host' ? 'device-level' : 'app-scoped'; | ||
| } | ||
| if (normalizedSetting === 'location') return locationAppScope(family, state); | ||
| if (normalizedSetting === 'mobile-data') return family === 'mobile' ? 'device-level' : 'unknown'; |
There was a problem hiding this comment.
P2: This classifies every state as device-level, so settings mobile-data cellular --app X is rejected as setting_app_not_consumed before Android can return its invalid-state error. Gate this on readSettingState(state) as locationAppScope does.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/contracts/src/settings.ts, line 227:
<comment>This classifies every state as device-level, so `settings mobile-data cellular --app X` is rejected as `setting_app_not_consumed` before Android can return its invalid-state error. Gate this on `readSettingState(state)` as `locationAppScope` does.</comment>
<file context>
@@ -223,6 +224,7 @@ export function settingsAppScope(
return family === 'macos-host' ? 'device-level' : 'app-scoped';
}
if (normalizedSetting === 'location') return locationAppScope(family, state);
+ if (normalizedSetting === 'mobile-data') return family === 'mobile' ? 'device-level' : 'unknown';
return 'unknown';
}
</file context>
| if (normalizedSetting === 'mobile-data') return family === 'mobile' ? 'device-level' : 'unknown'; | |
| if (normalizedSetting === 'mobile-data') { | |
| return family === 'mobile' && readSettingState(state) !== undefined ? 'device-level' : 'unknown'; | |
| } |
|
|
||
| - `settings animations off|on` (Android) sets the window, transition, and animator scales. Turn animations off to stabilize runs with heavy animation, and back on afterward. | ||
| - `settings airplane on|off` (Android) needs the connectivity service's `cmd connectivity airplane-mode` command, available from Android 11. It reports the resulting `airplaneMode`; a build without that command fails with `UNSUPPORTED_OPERATION` without changing anything. Connectivity takes a moment to settle, so poll the app rather than asserting offline behavior immediately. | ||
| - `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED` and `error.details.reason` `mobile-data-not-applied`, or `mobile-data-unreadable` when the row never reads as on or off. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over. |
There was a problem hiding this comment.
P2: mobile-data-unreadable is based on the final sample, not whether the row was unreadable throughout polling; a row that previously read 0 can later become absent and produce this reason. Describe it as applying when the final read is not 0 or 1.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At website/docs/docs/commands.md, line 670:
<comment>`mobile-data-unreadable` is based on the final sample, not whether the row was unreadable throughout polling; a row that previously read `0` can later become absent and produce this reason. Describe it as applying when the final read is not `0` or `1`.</comment>
<file context>
@@ -665,6 +667,9 @@ Device settings:
- `settings animations off|on` (Android) sets the window, transition, and animator scales. Turn animations off to stabilize runs with heavy animation, and back on afterward.
- `settings airplane on|off` (Android) needs the connectivity service's `cmd connectivity airplane-mode` command, available from Android 11. It reports the resulting `airplaneMode`; a build without that command fails with `UNSUPPORTED_OPERATION` without changing anything. Connectivity takes a moment to settle, so poll the app rather than asserting offline behavior immediately.
+- `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED` and `error.details.reason` `mobile-data-not-applied`, or `mobile-data-unreadable` when the row never reads as on or off. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over.
+- An emulator or device without an active SIM accepts `svc data` but can leave the stored value unchanged, so there the command fails with `COMMAND_FAILED` unless the value already matches. Such a target has no cellular network either: with Wi-Fi off it is offline rather than on cellular.
+- Mobile data is device-wide, so `--app` is refused with `setting_app_not_consumed`. Every other target refuses `mobile-data` before anything runs, because agent-device switches mobile data only through adb's `svc data`: physical iPhones, iOS, tvOS, and visionOS simulators, Limrun iOS, HarmonyOS, Vega, Linux, and web answer `UNSUPPORTED_OPERATION` with `error.details.reason: setting-unsupported-on-leaf`, and macOS answers `INVALID_ARGS` like its other unsupported settings.
</file context>
| - `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED` and `error.details.reason` `mobile-data-not-applied`, or `mobile-data-unreadable` when the row never reads as on or off. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over. | |
| - `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED`: `error.details.reason` is `mobile-data-unreadable` if the final row read is not `0` or `1`, or `mobile-data-not-applied` if that read is the opposite state. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over. |
| const deadline = Deadline.fromTimeoutMs(MOBILE_DATA_SETTLE_MS); | ||
| for (;;) { | ||
| signal?.throwIfAborted(); | ||
| const storedValue = await readAndroidGlobalSetting(device, settingKey); |
There was a problem hiding this comment.
P2: The settle deadline is checked only after this ADB read, which has no timeout; the normal executor can wait 15 seconds retrying an offline device. Bound each read with the remaining deadline and pass cancellation through.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-android/src/settings.ts, line 367:
<comment>The settle deadline is checked only after this ADB read, which has no timeout; the normal executor can wait 15 seconds retrying an offline device. Bound each read with the remaining deadline and pass cancellation through.</comment>
<file context>
@@ -285,6 +294,130 @@ function nearestTextSizeCategory(scale: number): (typeof TEXT_SIZE_CATEGORIES)[n
+ const deadline = Deadline.fromTimeoutMs(MOBILE_DATA_SETTLE_MS);
+ for (;;) {
+ signal?.throwIfAborted();
+ const storedValue = await readAndroidGlobalSetting(device, settingKey);
+ const mobileData = parseAndroidMobileData(storedValue);
+ if (mobileData === requested) return mobileData;
</file context>
| kind: 'emulator', | ||
| booted: true, | ||
| }, | ||
| { platform: 'linux', id: 'linux-host', name: 'Linux Desktop', kind: 'device', booted: true }, |
There was a problem hiding this comment.
P3: This list omits the supported vega and web platforms, so the test does not verify the pre-admission refusal for every non-Android target as its name claims. Add representative devices for both platforms.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/daemon/src/execution/handlers/__tests__/snapshot-settings-handler.test.ts, line 411:
<comment>This list omits the supported `vega` and `web` platforms, so the test does not verify the pre-admission refusal for every non-Android target as its name claims. Add representative devices for both platforms.</comment>
<file context>
@@ -372,6 +374,122 @@ test('settings usage hint documents canonical faceid states', async () => {
+ kind: 'emulator',
+ booted: true,
+ },
+ { platform: 'linux', id: 'linux-host', name: 'Linux Desktop', kind: 'device', booted: true },
+];
+
</file context>
| { platform: 'linux', id: 'linux-host', name: 'Linux Desktop', kind: 'device', booted: true }, | |
| { platform: 'linux', id: 'linux-host', name: 'Linux Desktop', kind: 'device', booted: true }, | |
| { platform: 'vega', id: 'vega-tv', name: 'Vega TV', kind: 'device', target: 'tv' }, | |
| { platform: 'web', id: 'web-browser', name: 'Browser', kind: 'device' }, |
| - `settings airplane on|off` (Android) needs the connectivity service's `cmd connectivity airplane-mode` command, available from Android 11. It reports the resulting `airplaneMode`; a build without that command fails with `UNSUPPORTED_OPERATION` without changing anything. Connectivity takes a moment to settle, so poll the app rather than asserting offline behavior immediately. | ||
| - `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED` and `error.details.reason` `mobile-data-not-applied`, or `mobile-data-unreadable` when the row never reads as on or off. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over. | ||
| - An emulator or device without an active SIM accepts `svc data` but can leave the stored value unchanged, so there the command fails with `COMMAND_FAILED` unless the value already matches. Such a target has no cellular network either: with Wi-Fi off it is offline rather than on cellular. | ||
| - Mobile data is device-wide, so `--app` is refused with `setting_app_not_consumed`. Every other target refuses `mobile-data` before anything runs, because agent-device switches mobile data only through adb's `svc data`: physical iPhones, iOS, tvOS, and visionOS simulators, Limrun iOS, HarmonyOS, Vega, Linux, and web answer `UNSUPPORTED_OPERATION` with `error.details.reason: setting-unsupported-on-leaf`, and macOS answers `INVALID_ARGS` like its other unsupported settings. |
There was a problem hiding this comment.
P3: Qualify the setting_app_not_consumed statement to adb-backed Android targets. Non-Android mobile-data --app requests hit the unsupported-target refusal before the app-scope check and return a different reason.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At website/docs/docs/commands.md, line 672:
<comment>Qualify the `setting_app_not_consumed` statement to adb-backed Android targets. Non-Android `mobile-data --app` requests hit the unsupported-target refusal before the app-scope check and return a different reason.</comment>
<file context>
@@ -665,6 +667,9 @@ Device settings:
- `settings airplane on|off` (Android) needs the connectivity service's `cmd connectivity airplane-mode` command, available from Android 11. It reports the resulting `airplaneMode`; a build without that command fails with `UNSUPPORTED_OPERATION` without changing anything. Connectivity takes a moment to settle, so poll the app rather than asserting offline behavior immediately.
+- `settings mobile-data on|off` (Android over adb) switches mobile data for the default data SIM with `svc data enable|disable` and reports the resulting `mobileData` as the device stores it: in the global `mobile_data` row, or in `mobile_data<subId>` for the default data subscription when more than one modem is active. If the stored value does not settle to the request within 2 seconds, it fails with `COMMAND_FAILED` and `error.details.reason` `mobile-data-not-applied`, or `mobile-data-unreadable` when the row never reads as on or off. Pair it with `settings wifi off` to move a session such as a video stream from Wi-Fi to cellular, and back with `settings wifi on`; poll the app while the network hands over.
+- An emulator or device without an active SIM accepts `svc data` but can leave the stored value unchanged, so there the command fails with `COMMAND_FAILED` unless the value already matches. Such a target has no cellular network either: with Wi-Fi off it is offline rather than on cellular.
+- Mobile data is device-wide, so `--app` is refused with `setting_app_not_consumed`. Every other target refuses `mobile-data` before anything runs, because agent-device switches mobile data only through adb's `svc data`: physical iPhones, iOS, tvOS, and visionOS simulators, Limrun iOS, HarmonyOS, Vega, Linux, and web answer `UNSUPPORTED_OPERATION` with `error.details.reason: setting-unsupported-on-leaf`, and macOS answers `INVALID_ARGS` like its other unsupported settings.
- `settings text-size` reads the current size; `settings text-size <category>` sets one: `extra-small`, `small`, `medium`, `large`, `extra-large`, `extra-extra-large`, `extra-extra-extra-large`, `accessibility-medium`, `accessibility-large`, `accessibility-extra-large`, `accessibility-extra-extra-large`, or `accessibility-extra-extra-extra-large`. It works on iPhone and iPad simulators and on Android; every other target refuses both read and write. The read reports `category` and the target's own `platformValue` (iOS may capitalize it, for example `extra-Small`).
- Android has only a font-scale multiplier, mapped so `large` is `1.0` and `accessibility-extra-extra-extra-large` is `3.2`. A value between rungs reads as the nearest category, with the exact multiplier in `platformValue`; an unset scale reads as `large`.
</file context>
| - Mobile data is device-wide, so `--app` is refused with `setting_app_not_consumed`. Every other target refuses `mobile-data` before anything runs, because agent-device switches mobile data only through adb's `svc data`: physical iPhones, iOS, tvOS, and visionOS simulators, Limrun iOS, HarmonyOS, Vega, Linux, and web answer `UNSUPPORTED_OPERATION` with `error.details.reason: setting-unsupported-on-leaf`, and macOS answers `INVALID_ARGS` like its other unsupported settings. | |
| - On adb-backed Android, mobile data is device-wide, so `--app` is refused with `setting_app_not_consumed`. Every other target refuses `mobile-data` before anything runs, because agent-device switches mobile data only through adb's `svc data`: physical iPhones, iOS, tvOS, and visionOS simulators, Limrun iOS, HarmonyOS, Vega, Linux, and web answer `UNSUPPORTED_OPERATION` with `error.details.reason: setting-unsupported-on-leaf`, and macOS answers `INVALID_ARGS` like its other unsupported settings. |
|
Thanks for this. The feature is useful, but the PR is large for one Android switch: +752 lines, and only about 60 of them switch mobile data. Here is what I think could be smaller, reviewed at 6b94f11.
None of this changes the behavior. It is about keeping the patch at the size of the feature. |
|
Thanks for the PR. At 6b94f11 I found one problem that should be fixed before merge. The Android single-modem path is covered by the live run in the PR body, which I accepted from the description and did not re-run. Both Smoke Tests jobs were still pending when I checked, so I cannot say yet what CI shows. I have no conflict information. The multi-SIM branch in resolveAndroidMobileDataSettingKey picks Could the feature be much smaller? A Not blocking, and you can take or leave it: the owner-side refusals in app-settings.ts and the Limrun iOS settings code repeat the refusal the daemon already makes at snapshot-settings.ts:347, so three sites now refuse one setting. Keeping the daemon refusal and deleting the copies would be simpler, unless a caller reaches On the open inline threads, the first four hold. The signal is not checked before the key probe or Before merge, remove the unvalidated multi-SIM branch or post the dual-SIM run, then answer the size questions above. |
Summary
Adds
settings mobile-data on|offfor Android, so a run on a streaming app can move from Wi-Fi to cellular and back and check the "streaming on mobile data" warnings.agent-device settings wifi off agent-device settings mobile-data on # mobileData: enabledNode:
client.settings.update({ setting: 'mobile-data', state: 'off' })mobile_data<subId>with more than one active modem (persist.radio.multisim.config), otherwisemobile_data. Then runssvc data enable|disableand polls that row for up to 2 s.mobile-data-not-appliedormobile-data-unreadable, and says whether the switch was dispatched.UNSUPPORTED_OPERATION(setting-unsupported-on-leaf).--appis refused withsetting_app_not_consumed.Test plan
Use an Android emulator or phone with a SIM.
Sis its adb serial.pnpm build && pnpm build:android && pnpm clean:daemonpnpm ad settings mobile-data off --platform android --serial $S --jsonreturnsmobileData: disabled, andadb -s $S shell settings get global mobile_dataprints 0.onreturnsenabledand 1.settings wifi off, thenmobile-data on.adb -s $S shell ping -c 2 8.8.8.8gets replies. Aftermobile-data offit loses every packet.mobile-data off --app com.example.appfails withINVALID_ARGSandsetting_app_not_consumed. The row stays 1.settings wifi onandmobile-data on.Validation
pnpm check:affected --runpassed at6b94f115. Steps 2 to 4 ran at124d05f5(same tree) on an Android 37 emulator with a SIM and an empty multisim config. All passed, and the emulator was restored.Not covered: the multi-modem row choice. It follows AOSP
GlobalSettingsHelperand needs a dual-SIM phone.