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 {
|
||||
c += 0.1;
|
||||
}
|
||||
// Cross-sensor disagreement: proximity says near but accelerometer
|
||||
// says moving — user is walking with phone in hand, not a pocket.
|
||||
// Don't suppress DPMS wake in this case.
|
||||
// Proximity near while the device is moving is genuinely ambiguous —
|
||||
// a phone walking in a pocket and a phone held to an ear read the
|
||||
// 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 {
|
||||
c -= 0.2; // reduce confidence — ambiguous
|
||||
c -= 0.2;
|
||||
}
|
||||
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?
|
||||
pub fn should_promote_idle_faster(&self) -> bool {
|
||||
self.confidence() >= 0.6
|
||||
|
|
@ -1067,29 +1063,16 @@ impl DeviceStateMachine {
|
|||
return actions;
|
||||
}
|
||||
|
||||
// Pocket or face-down: something is over the sensor while the session
|
||||
// is locked. Blank now rather than waiting out the idle budget — this
|
||||
// is the job `blueline-proximity-lock` used to do by calling the DPMS
|
||||
// executor itself, and it belongs here where it can be weighed
|
||||
// against the rest of the evidence instead of racing the other rules.
|
||||
// Proximity does not blank the panel. It used to, on any locked
|
||||
// screen, which is `blueline-proximity-lock`'s old job moved inward
|
||||
// and kept too powerful: a covered sensor is a pocket, a face, a
|
||||
// table, or a thumb, and the machine cannot tell which. Turning the
|
||||
// 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
|
||||
// device is moving (walking with it, or holding it to an ear): a lit
|
||||
// panel against a face or a pocket lining is not wanted either way.
|
||||
// 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",
|
||||
);
|
||||
}
|
||||
// What remains of proximity is evidence: it drives `Observed`, it is
|
||||
// in every snapshot, and it gates tap-to-wake (`suppress_wake`). The
|
||||
// idle budget below blanks a locked screen soon enough anyway.
|
||||
|
||||
// The compositor says the user is interacting. Not our business yet —
|
||||
// and crucially this is what keeps a long swipe from being blanked
|
||||
|
|
@ -1444,7 +1427,7 @@ impl DeviceStateMachine {
|
|||
shell_alive,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked,
|
||||
screen_lock_secure,
|
||||
|
|
@ -1458,6 +1441,26 @@ impl DeviceStateMachine {
|
|||
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
|
||||
/// legal and applied, false if refused (logged as a warning).
|
||||
/// Emits a forensic entry for every attempt (legal or not).
|
||||
|
|
@ -1483,7 +1486,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1516,7 +1519,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1639,7 +1642,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1693,7 +1696,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1715,7 +1718,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1744,7 +1747,7 @@ impl DeviceStateMachine {
|
|||
shell_alive: false,
|
||||
proximity_near: self.sensor_evidence.proximity_near,
|
||||
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(),
|
||||
screen_locked: false,
|
||||
screen_lock_secure: false,
|
||||
|
|
@ -1770,7 +1773,7 @@ impl DeviceStateMachine {
|
|||
"display_active": is_display_active(self.state),
|
||||
"observed": matches!(self.state, DeviceState::Observed),
|
||||
"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,
|
||||
"user_active": self.user_active(),
|
||||
"idle_secs": self.idle_secs(),
|
||||
|
|
@ -1881,22 +1884,6 @@ mod tests {
|
|||
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]
|
||||
fn unlocking_cancels_a_pending_blank_and_restores_the_dim() {
|
||||
let (mut sm, t0) = locked_and_lit();
|
||||
|
|
@ -2069,26 +2056,22 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn proximity_near_while_locked_blanks_at_once() {
|
||||
let (mut sm, t0) = locked_and_lit();
|
||||
sm.sensor_evidence.proximity_near = true;
|
||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
||||
// No waiting for the idle budget: it is in a pocket now.
|
||||
assert_eq!(
|
||||
sm.tick_at(t0 + Duration::from_millis(10)),
|
||||
vec![Action::Blank]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proximity_near_while_unlocked_is_left_alone() {
|
||||
let mut sm = DeviceStateMachine::new();
|
||||
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());
|
||||
fn proximity_alone_never_blanks_the_panel() {
|
||||
// A covered sensor is a pocket, a face, a table or a thumb, and the
|
||||
// machine cannot tell which. The only reading that justifies turning
|
||||
// the screen off is a call, and the machine has no call state yet.
|
||||
for locked in [true, false] {
|
||||
let (mut sm, t0) = locked_and_lit();
|
||||
if !locked {
|
||||
sm.set_session_locked(false);
|
||||
}
|
||||
sm.sensor_evidence.proximity_near = true;
|
||||
sm.mark_evidence_seen(SensorSource::Proximity);
|
||||
assert!(
|
||||
sm.tick_at(t0 + Duration::from_millis(10)).is_empty(),
|
||||
"locked={locked}: proximity is evidence, not an actuator"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2147,11 +2130,11 @@ mod tests {
|
|||
let (mut sm, t0) = locked_and_lit();
|
||||
sm.sensor_evidence.proximity_near = true;
|
||||
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.
|
||||
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]
|
||||
|
|
@ -2260,7 +2243,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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 {
|
||||
proximity_near: true,
|
||||
accel_moving: true,
|
||||
|
|
@ -2269,12 +2253,27 @@ mod tests {
|
|||
};
|
||||
// 0.4 + 0.3 - 0.2 = 0.5
|
||||
assert!((e.confidence() - 0.5).abs() < 0.01);
|
||||
// DPMS wake suppression still on (0.5 >= 0.3)
|
||||
assert!(e.should_suppress_dpms_wake());
|
||||
// But idle promotion not accelerated (0.5 < 0.6)
|
||||
// Idle promotion not accelerated (0.5 < 0.6).
|
||||
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]
|
||||
fn proximity_triggers_observed() {
|
||||
let mut sm = DeviceStateMachine::new();
|
||||
|
|
|
|||
|
|
@ -605,7 +605,6 @@ fn handle_request(
|
|||
}
|
||||
}
|
||||
let conf = evidence.confidence();
|
||||
let suppress = evidence.should_suppress_dpms_wake();
|
||||
let promote = evidence.should_promote_idle_faster();
|
||||
// Copy the readings out, ending the borrow before the machine is
|
||||
// touched again.
|
||||
|
|
@ -619,13 +618,18 @@ fn handle_request(
|
|||
// on the next tick, because "never reported" and "reported long
|
||||
// ago" are the same thing to the staleness rule.
|
||||
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!(
|
||||
"[device-state] sensor {:?} = {:?} (confidence={:.2}, suppress_dpms={}, promote_idle={})",
|
||||
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!({
|
||||
"ok": true,
|
||||
"confidence": conf,
|
||||
|
|
|
|||
Loading…
Reference in a new issue