sessiond: proximity stops actuating; it vetoes tap-to-wake and nothing else
This commit is contained in:
parent
4d0c7a1c0b
commit
813010dbb6
2 changed files with 88 additions and 85 deletions
|
|
@ -952,20 +952,16 @@ impl SensorEvidence {
|
||||||
if self.touch_active {
|
if self.touch_active {
|
||||||
c += 0.1;
|
c += 0.1;
|
||||||
}
|
}
|
||||||
// Cross-sensor disagreement: proximity says near but accelerometer
|
// Proximity near while the device is moving is genuinely ambiguous —
|
||||||
// says moving — user is walking with phone in hand, not a pocket.
|
// a phone walking in a pocket and a phone held to an ear read the
|
||||||
// Don't suppress DPMS wake in this case.
|
// same. It lowers confidence and nothing more; it does not decide the
|
||||||
|
// wake, which is a question about the session, not the sensors.
|
||||||
if self.proximity_near && self.accel_moving {
|
if self.proximity_near && self.accel_moving {
|
||||||
c -= 0.2; // reduce confidence — ambiguous
|
c -= 0.2;
|
||||||
}
|
}
|
||||||
c.clamp(0.0, 1.0)
|
c.clamp(0.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should DPMS wake be suppressed? (prevent pocket-dial)
|
|
||||||
pub fn should_suppress_dpms_wake(&self) -> bool {
|
|
||||||
self.proximity_near && self.confidence() >= 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should idle tier promotion be accelerated?
|
/// Should idle tier promotion be accelerated?
|
||||||
pub fn should_promote_idle_faster(&self) -> bool {
|
pub fn should_promote_idle_faster(&self) -> bool {
|
||||||
self.confidence() >= 0.6
|
self.confidence() >= 0.6
|
||||||
|
|
@ -1067,29 +1063,16 @@ impl DeviceStateMachine {
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pocket or face-down: something is over the sensor while the session
|
// Proximity does not blank the panel. It used to, on any locked
|
||||||
// is locked. Blank now rather than waiting out the idle budget — this
|
// screen, which is `blueline-proximity-lock`'s old job moved inward
|
||||||
// is the job `blueline-proximity-lock` used to do by calling the DPMS
|
// and kept too powerful: a covered sensor is a pocket, a face, a
|
||||||
// executor itself, and it belongs here where it can be weighed
|
// table, or a thumb, and the machine cannot tell which. Turning the
|
||||||
// against the rest of the evidence instead of racing the other rules.
|
// screen off on that reading is only right during a call — and the
|
||||||
|
// machine has no call state yet, so for now it is never right.
|
||||||
//
|
//
|
||||||
// Note this deliberately fires even when the accelerometer says the
|
// What remains of proximity is evidence: it drives `Observed`, it is
|
||||||
// device is moving (walking with it, or holding it to an ear): a lit
|
// in every snapshot, and it gates tap-to-wake (`suppress_wake`). The
|
||||||
// panel against a face or a pocket lining is not wanted either way.
|
// idle budget below blanks a locked screen soon enough anyway.
|
||||||
// The hand-held carve-out in §4 is about not suppressing a *wake*,
|
|
||||||
// which is a different question from blanking one that is already on.
|
|
||||||
if is_locked(self.state) && self.sensor_evidence.proximity_near {
|
|
||||||
info!("[device-state] proximity near while locked — blanking");
|
|
||||||
return self.request_blank(
|
|
||||||
now,
|
|
||||||
serde_json::json!({
|
|
||||||
"reason": "proximity-near",
|
|
||||||
"confidence": self.sensor_evidence.confidence(),
|
|
||||||
"accel_moving": self.sensor_evidence.accel_moving,
|
|
||||||
}),
|
|
||||||
"locked and something is over the proximity sensor",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The compositor says the user is interacting. Not our business yet —
|
// The compositor says the user is interacting. Not our business yet —
|
||||||
// and crucially this is what keeps a long swipe from being blanked
|
// and crucially this is what keeps a long swipe from being blanked
|
||||||
|
|
@ -1444,7 +1427,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive,
|
shell_alive,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked,
|
screen_locked,
|
||||||
screen_lock_secure,
|
screen_lock_secure,
|
||||||
|
|
@ -1458,6 +1441,26 @@ impl DeviceStateMachine {
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should this wake be refused because something is over the sensor?
|
||||||
|
///
|
||||||
|
/// Tap-to-wake only. A double tap is the one wake source a pocket can
|
||||||
|
/// produce by itself, so a covered sensor is the right veto for it. A
|
||||||
|
/// power button press is intent (§4: "no — hardware signal") and is never
|
||||||
|
/// refused; neither is a wake the machine itself asked for.
|
||||||
|
///
|
||||||
|
/// This used to be a confidence question — the pair proximity-near +
|
||||||
|
/// accel-moving read as 0.5 and suppressed everything. Motion cannot tell
|
||||||
|
/// a pocket from an ear from a hand, so it was never the instrument.
|
||||||
|
///
|
||||||
|
/// **Owed:** a call. Screen dark at the ear is wanted whether or not the
|
||||||
|
/// session is locked, and it is the only case where proximity should turn
|
||||||
|
/// a panel *off* rather than decline to turn one on. That needs a
|
||||||
|
/// call-state input (ModemManager / callaudiod) — a factor, never an
|
||||||
|
/// authority.
|
||||||
|
pub fn suppress_wake(&self, trigger: InputTrigger) -> bool {
|
||||||
|
matches!(trigger, InputTrigger::DoubleTapToWake) && self.sensor_evidence.proximity_near
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt a state transition. Returns true if the transition was
|
/// Attempt a state transition. Returns true if the transition was
|
||||||
/// legal and applied, false if refused (logged as a warning).
|
/// legal and applied, false if refused (logged as a warning).
|
||||||
/// Emits a forensic entry for every attempt (legal or not).
|
/// Emits a forensic entry for every attempt (legal or not).
|
||||||
|
|
@ -1483,7 +1486,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1516,7 +1519,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1639,7 +1642,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence,
|
confidence,
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1693,7 +1696,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1715,7 +1718,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1744,7 +1747,7 @@ impl DeviceStateMachine {
|
||||||
shell_alive: false,
|
shell_alive: false,
|
||||||
proximity_near: self.sensor_evidence.proximity_near,
|
proximity_near: self.sensor_evidence.proximity_near,
|
||||||
confidence: self.sensor_evidence.confidence(),
|
confidence: self.sensor_evidence.confidence(),
|
||||||
suppress_dpms_wake: self.sensor_evidence.should_suppress_dpms_wake(),
|
suppress_dpms_wake: self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
promote_idle_faster: self.sensor_evidence.should_promote_idle_faster(),
|
||||||
screen_locked: false,
|
screen_locked: false,
|
||||||
screen_lock_secure: false,
|
screen_lock_secure: false,
|
||||||
|
|
@ -1770,7 +1773,7 @@ impl DeviceStateMachine {
|
||||||
"display_active": is_display_active(self.state),
|
"display_active": is_display_active(self.state),
|
||||||
"observed": matches!(self.state, DeviceState::Observed),
|
"observed": matches!(self.state, DeviceState::Observed),
|
||||||
"observed_confidence": self.sensor_evidence.confidence(),
|
"observed_confidence": self.sensor_evidence.confidence(),
|
||||||
"suppress_dpms_wake": self.sensor_evidence.should_suppress_dpms_wake(),
|
"suppress_dpms_wake": self.suppress_wake(InputTrigger::DoubleTapToWake),
|
||||||
"panel_on": self.panel_on,
|
"panel_on": self.panel_on,
|
||||||
"user_active": self.user_active(),
|
"user_active": self.user_active(),
|
||||||
"idle_secs": self.idle_secs(),
|
"idle_secs": self.idle_secs(),
|
||||||
|
|
@ -1881,22 +1884,6 @@ mod tests {
|
||||||
assert!(!is_locked(sm.state));
|
assert!(!is_locked(sm.state));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn proximity_cannot_blank_after_a_logind_unlock() {
|
|
||||||
// The symptom Casey reported: proximity blanking an unlocked phone.
|
|
||||||
// The proximity rule was always correctly gated on is_locked — the
|
|
||||||
// state was the thing lying.
|
|
||||||
let (mut sm, t0) = locked_and_lit();
|
|
||||||
sm.set_session_locked(false);
|
|
||||||
|
|
||||||
sm.sensor_evidence.proximity_near = true;
|
|
||||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
|
||||||
assert!(
|
|
||||||
sm.tick_at(t0 + Duration::from_secs(1)).is_empty(),
|
|
||||||
"a hand over the sensor must not blank an unlocked phone"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unlocking_cancels_a_pending_blank_and_restores_the_dim() {
|
fn unlocking_cancels_a_pending_blank_and_restores_the_dim() {
|
||||||
let (mut sm, t0) = locked_and_lit();
|
let (mut sm, t0) = locked_and_lit();
|
||||||
|
|
@ -2069,26 +2056,22 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn proximity_near_while_locked_blanks_at_once() {
|
fn proximity_alone_never_blanks_the_panel() {
|
||||||
let (mut sm, t0) = locked_and_lit();
|
// A covered sensor is a pocket, a face, a table or a thumb, and the
|
||||||
sm.sensor_evidence.proximity_near = true;
|
// machine cannot tell which. The only reading that justifies turning
|
||||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
// the screen off is a call, and the machine has no call state yet.
|
||||||
// No waiting for the idle budget: it is in a pocket now.
|
for locked in [true, false] {
|
||||||
assert_eq!(
|
let (mut sm, t0) = locked_and_lit();
|
||||||
sm.tick_at(t0 + Duration::from_millis(10)),
|
if !locked {
|
||||||
vec![Action::Blank]
|
sm.set_session_locked(false);
|
||||||
);
|
}
|
||||||
}
|
sm.sensor_evidence.proximity_near = true;
|
||||||
|
sm.mark_evidence_seen(SensorSource::Proximity);
|
||||||
#[test]
|
assert!(
|
||||||
fn proximity_near_while_unlocked_is_left_alone() {
|
sm.tick_at(t0 + Duration::from_millis(10)).is_empty(),
|
||||||
let mut sm = DeviceStateMachine::new();
|
"locked={locked}: proximity is evidence, not an actuator"
|
||||||
let t0 = Instant::now();
|
);
|
||||||
sm.sensor_evidence.proximity_near = true;
|
}
|
||||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
|
||||||
// A hand over the sensor on an unlocked, in-use phone is not a
|
|
||||||
// reason to blank it.
|
|
||||||
assert!(sm.tick_at(t0 + Duration::from_secs(1)).is_empty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2147,11 +2130,11 @@ mod tests {
|
||||||
let (mut sm, t0) = locked_and_lit();
|
let (mut sm, t0) = locked_and_lit();
|
||||||
sm.sensor_evidence.proximity_near = true;
|
sm.sensor_evidence.proximity_near = true;
|
||||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
sm.mark_evidence_seen(SensorSource::Proximity);
|
||||||
assert!(sm.sensor_evidence.should_suppress_dpms_wake());
|
assert!(sm.suppress_wake(InputTrigger::DoubleTapToWake));
|
||||||
|
|
||||||
// A dead proximity sensor must not keep vetoing wakes forever.
|
// A dead proximity sensor must not keep vetoing wakes forever.
|
||||||
sm.tick_at(t0 + EVIDENCE_TTL + Duration::from_secs(1));
|
sm.tick_at(t0 + EVIDENCE_TTL + Duration::from_secs(1));
|
||||||
assert!(!sm.sensor_evidence.should_suppress_dpms_wake());
|
assert!(!sm.suppress_wake(InputTrigger::DoubleTapToWake));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2260,7 +2243,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sensor_disagreement_reduces_confidence() {
|
fn sensor_disagreement_reduces_confidence() {
|
||||||
// proximity near + accel moving = walking with phone, not pocket
|
// Proximity near + accel moving: a pocket, an ear and a hand all read
|
||||||
|
// this way. It lowers confidence and decides nothing.
|
||||||
let e = SensorEvidence {
|
let e = SensorEvidence {
|
||||||
proximity_near: true,
|
proximity_near: true,
|
||||||
accel_moving: true,
|
accel_moving: true,
|
||||||
|
|
@ -2269,12 +2253,27 @@ mod tests {
|
||||||
};
|
};
|
||||||
// 0.4 + 0.3 - 0.2 = 0.5
|
// 0.4 + 0.3 - 0.2 = 0.5
|
||||||
assert!((e.confidence() - 0.5).abs() < 0.01);
|
assert!((e.confidence() - 0.5).abs() < 0.01);
|
||||||
// DPMS wake suppression still on (0.5 >= 0.3)
|
// Idle promotion not accelerated (0.5 < 0.6).
|
||||||
assert!(e.should_suppress_dpms_wake());
|
|
||||||
// But idle promotion not accelerated (0.5 < 0.6)
|
|
||||||
assert!(!e.should_promote_idle_faster());
|
assert!(!e.should_promote_idle_faster());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_tap_to_wake_is_vetoed_by_a_covered_sensor() {
|
||||||
|
// A double tap is the one wake a pocket can produce by itself. The
|
||||||
|
// power button is intent and is never refused, whatever the sensors
|
||||||
|
// say, locked or not.
|
||||||
|
let mut sm = DeviceStateMachine::new();
|
||||||
|
sm.sensor_evidence.proximity_near = true;
|
||||||
|
sm.sensor_evidence.accel_moving = true;
|
||||||
|
|
||||||
|
for state in [DeviceState::Active, DeviceState::Locked] {
|
||||||
|
sm.transition(state);
|
||||||
|
assert!(sm.suppress_wake(InputTrigger::DoubleTapToWake));
|
||||||
|
assert!(!sm.suppress_wake(InputTrigger::PowerButton));
|
||||||
|
assert!(!sm.suppress_wake(InputTrigger::Touch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn proximity_triggers_observed() {
|
fn proximity_triggers_observed() {
|
||||||
let mut sm = DeviceStateMachine::new();
|
let mut sm = DeviceStateMachine::new();
|
||||||
|
|
|
||||||
|
|
@ -605,7 +605,6 @@ fn handle_request(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let conf = evidence.confidence();
|
let conf = evidence.confidence();
|
||||||
let suppress = evidence.should_suppress_dpms_wake();
|
|
||||||
let promote = evidence.should_promote_idle_faster();
|
let promote = evidence.should_promote_idle_faster();
|
||||||
// Copy the readings out, ending the borrow before the machine is
|
// Copy the readings out, ending the borrow before the machine is
|
||||||
// touched again.
|
// touched again.
|
||||||
|
|
@ -619,13 +618,18 @@ fn handle_request(
|
||||||
// on the next tick, because "never reported" and "reported long
|
// on the next tick, because "never reported" and "reported long
|
||||||
// ago" are the same thing to the staleness rule.
|
// ago" are the same thing to the staleness rule.
|
||||||
d.device_state.mark_evidence_seen(input.source);
|
d.device_state.mark_evidence_seen(input.source);
|
||||||
|
// Let the state machine evaluate Observed transitions.
|
||||||
|
d.device_state
|
||||||
|
.update_sensors_from(input.source, evidence_copy);
|
||||||
|
// The tap-to-wake answer specifically; a power button is never
|
||||||
|
// refused. Asked after the update, not before.
|
||||||
|
let suppress = d
|
||||||
|
.device_state
|
||||||
|
.suppress_wake(InputTrigger::DoubleTapToWake);
|
||||||
info!(
|
info!(
|
||||||
"[device-state] sensor {:?} = {:?} (confidence={:.2}, suppress_dpms={}, promote_idle={})",
|
"[device-state] sensor {:?} = {:?} (confidence={:.2}, suppress_dpms={}, promote_idle={})",
|
||||||
input.source, input.value, conf, suppress, promote
|
input.source, input.value, conf, suppress, promote
|
||||||
);
|
);
|
||||||
// Let the state machine evaluate Observed transitions.
|
|
||||||
d.device_state
|
|
||||||
.update_sensors_from(input.source, evidence_copy);
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"confidence": conf,
|
"confidence": conf,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue