Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
109 lines
5.4 KiB
Plaintext
109 lines
5.4 KiB
Plaintext
/* ── the phone, turned over: the two settings that do it ───────────────
|
|
* `settings get system accelerometer_rotation` and `user_rotation` are the
|
|
* only two things on the device this walk ever writes, and both are READ
|
|
* before either is written. The saved pair is latched at module scope on
|
|
* purpose: the step loop's `still()` and its PROBE call are wrapped in
|
|
* nothing at all, so a WebView that stops answering rejects after cdp.mjs's
|
|
* timeout and throws clean out of the walk into main's `finally`. That is
|
|
* the exact path that would otherwise leave Kyle's phone locked sideways,
|
|
* which is why the restore is a latch reachable from four places — the
|
|
* rotation block's own finally, main's finally, process 'exit' and a
|
|
* signal — rather than a step in a list. */
|
|
let rotationSaved = null;
|
|
|
|
/** Both rotation settings as the device has them now, or null when either
|
|
* will not read as something we recognise. null is the SKIP signal: the
|
|
* block never writes what it could not read first. `user_rotation` comes
|
|
* back as the literal "null" on a phone where it has never been set. */
|
|
function rotationSettings() {
|
|
try {
|
|
const accel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
|
|
const user = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
|
|
if (!/^\d+$/.test(accel)) return null;
|
|
if (!/^\d+$/.test(user) && user !== "null") return null;
|
|
return { accel, user };
|
|
} catch { return null; }
|
|
}
|
|
|
|
/** Read once and latch. Returns the pair, or null when the device would not
|
|
* say — in which case nothing has been written and there is nothing to put
|
|
* back. */
|
|
function rotationArm() {
|
|
const was = rotationSettings();
|
|
if (was) rotationSaved = was;
|
|
return was;
|
|
}
|
|
|
|
/** Drop the latch WITHOUT writing anything. For the skip paths: the pair has
|
|
* been read but nothing has been put, and restoring there would write two
|
|
* settings onto Kyle's phone that this walk never changed. */
|
|
function rotationDisarm() { rotationSaved = null; }
|
|
|
|
/** Hold the phone at one rotation. Auto-rotate goes off FIRST: with the
|
|
* sensor still in charge it takes `user_rotation` straight back and the
|
|
* turn never happens. Returns null, or the message it failed with. */
|
|
function rotationPut(n) {
|
|
try {
|
|
adb("shell", "settings", "put", "system", "accelerometer_rotation", "0");
|
|
adb("shell", "settings", "put", "system", "user_rotation", String(n));
|
|
return null;
|
|
} catch (e) { return e.message; }
|
|
}
|
|
|
|
/** Put both back, once. The latch is cleared BEFORE anything is written, so
|
|
* the first caller is the only one that acts and the three behind it are
|
|
* no-ops; a restore that fails puts the latch back so the next caller can
|
|
* try again. The rotation goes first and auto-rotate last — a phone whose
|
|
* auto-rotate was on must hand control back to the sensor only once the
|
|
* angle underneath it is the one it started at. Synchronous, because
|
|
* `adb()` is spawnSync and nothing asynchronous survives an 'exit'
|
|
* handler. Returns true, null when there was nothing to restore, or the
|
|
* message it failed with. */
|
|
function rotationRestore() {
|
|
const was = rotationSaved;
|
|
rotationSaved = null;
|
|
if (!was) return null;
|
|
try {
|
|
if (was.user === "null") {
|
|
/* never set on this phone: delete it rather than inventing a 0 */
|
|
try { adb("shell", "settings", "delete", "system", "user_rotation"); }
|
|
catch { adb("shell", "settings", "put", "system", "user_rotation", "0"); }
|
|
} else {
|
|
adb("shell", "settings", "put", "system", "user_rotation", was.user);
|
|
}
|
|
adb("shell", "settings", "put", "system", "accelerometer_rotation", was.accel);
|
|
return true;
|
|
} catch (e) { rotationSaved = was; return e.message; }
|
|
}
|
|
|
|
/** The system's own view of the turn, for the report only. The two settings
|
|
* read back, plus the display's current size and rotation out of `dumpsys
|
|
* window displays` — whose spelling moves between Android versions, so a
|
|
* parse failure comes back empty and is printed, never asserted. */
|
|
function rotationSystem() {
|
|
const out = { setAccel: "", setUser: "", dispCur: "", dispRot: "" };
|
|
try {
|
|
out.setAccel = adb("shell", "settings", "get", "system", "accelerometer_rotation").toString().trim();
|
|
out.setUser = adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
|
|
} catch { /* empty is the reading */ }
|
|
try {
|
|
const d = adb("shell", "dumpsys", "window", "displays").toString();
|
|
out.dispCur = (d.match(/\bcur=(\d+x\d+)/) || [])[1] || "";
|
|
out.dispRot = (d.match(/mCurrentRotation=(?:ROTATION_)?(\w+)/) || d.match(/\brotation=(\d+)/) || [])[1] || "";
|
|
} catch { /* empty is the reading */ }
|
|
return out;
|
|
}
|
|
|
|
/* 'exit' does not run on a signal and a signal does not run 'exit', so
|
|
both. Ctrl-C in the middle of the turn is precisely the case that would
|
|
otherwise leave the phone sideways until Kyle found the Quick Settings
|
|
tile. Registered only for the phone walk — the mock never writes a
|
|
setting, and a SIGINT handler on the mock run would change how Ctrl-C
|
|
behaves there for nothing. */
|
|
if (PHONE) {
|
|
process.on("exit", () => { rotationRestore(); });
|
|
for (const sig of ["SIGINT", "SIGTERM", "SIGBREAK"]) {
|
|
process.on(sig, () => { rotationRestore(); process.exit(130); });
|
|
}
|
|
}
|