1use fuchsia_sync::Mutex;
6use futures::StreamExt;
7use log::{debug, info, warn};
8use std::sync::{Arc, LazyLock};
9use zx;
10
11use fidl::endpoints::create_request_stream;
12use fidl_fuchsia_ui_input::MediaButtonsEvent;
13use fidl_fuchsia_ui_policy as fuipolicy;
14
15static MAX_PRESS_INTERVAL_NS: LazyLock<i64> = LazyLock::new(|| 500 * 1_000_000); const REQUIRED_PRESS_COUNT: u32 = 5;
17
18pub trait Clock: Send + Sync {
19 fn now_ns(&self) -> i64;
20}
21
22pub struct BootClock;
23
24impl Clock for BootClock {
25 fn now_ns(&self) -> i64 {
26 zx::BootInstant::get().into_nanos()
27 }
28}
29
30#[derive(Debug)]
31struct ButtonPressState {
32 count: u32,
33 last_press_time_ns: i64,
34 power_was_pressed: bool,
35 #[cfg(test)]
36 action_triggered_count: u32,
37}
38
39impl ButtonPressState {
40 fn new() -> Self {
41 Self {
42 count: 0,
43 last_press_time_ns: 0,
44 power_was_pressed: false,
45 #[cfg(test)]
46 action_triggered_count: 0,
47 }
48 }
49}
50
51pub struct DebugState<C: Clock> {
52 debug_enabled: bool,
54 button_press_state: Mutex<ButtonPressState>,
55 clock: C,
56}
57
58pub type DebugManager = DebugState<BootClock>;
60
61impl DebugState<BootClock> {
62 pub fn new(debug_enabled: bool) -> Self {
63 Self {
64 debug_enabled,
65 button_press_state: Mutex::new(ButtonPressState::new()),
66 clock: BootClock,
67 }
68 }
69}
70
71impl<C: Clock + 'static> DebugState<C> {
72 #[cfg(test)]
73 fn new_for_test(debug_enabled: bool, clock: C) -> Self {
74 Self { debug_enabled, button_press_state: Mutex::new(ButtonPressState::new()), clock }
75 }
76
77 pub fn start_media_buttons_listener(self: Arc<Self>) {
78 if !self.debug_enabled {
79 debug!("Debug mode not enabled, skipping media button listener registration.");
80 return;
81 }
82
83 info!("Registering for media button events to enable 5-button press for debug.");
84 fuchsia_async::Task::spawn(async move {
85 match fuchsia_component::client::connect_to_protocol::<
86 fuipolicy::DeviceListenerRegistryMarker,
87 >() {
88 Ok(device_listener_registry) => {
89 let (client_end, mut stream) =
90 create_request_stream::<fuipolicy::MediaButtonsListenerMarker>();
91 if let Err(e) = device_listener_registry.register_listener(client_end).await {
92 warn!("Failed to register media buttons listener: {e:?}");
93 return;
94 }
95 while let Some(Ok(request)) = stream.next().await {
96 if let fuipolicy::MediaButtonsListenerRequest::OnEvent {
97 event,
98 responder,
99 } = request
100 {
101 if self.process_button_event(&event) {
102 debug!("Detected 5 function button presses in a row.");
104 }
105 if let Err(e) = responder.send() {
106 warn!("Failed to send response for media buttons event: {e:?}");
107 }
108 }
109 }
110 }
111 Err(e) => {
112 warn!("Failed to connect to fuchsia.ui.policy.DeviceListenerRegistry: {e:?}");
113 }
114 }
115 })
116 .detach();
117 }
118
119 fn process_button_event(&self, event: &MediaButtonsEvent) -> bool {
120 let mut state = self.button_press_state.lock();
121 if let Some(power_is_pressed) = event.power
122 && (power_is_pressed || state.power_was_pressed)
123 {
124 debug!("Detected overlapping POWER button activity; resetting FUNCTION button counter");
125 state.power_was_pressed = power_is_pressed;
126 state.count = 0;
127 return false;
128 }
129
130 if event.function != Some(true) {
131 return false;
133 }
134
135 let now_ns = self.clock.now_ns();
137
138 if now_ns - state.last_press_time_ns > *MAX_PRESS_INTERVAL_NS {
139 state.count = 1;
140 } else {
141 state.count += 1;
142 }
143
144 state.last_press_time_ns = now_ns;
145
146 if state.count >= REQUIRED_PRESS_COUNT {
147 state.count = 0;
148 #[cfg(test)]
149 {
150 state.action_triggered_count += 1;
151 }
152 return true;
153 }
154 false
155 }
156
157 pub fn stop(&self) {
158 if !self.debug_enabled {
159 return;
160 }
161 debug!("Resetting debug button press state.");
162 *self.button_press_state.lock() = ButtonPressState::new();
163 }
164
165 #[cfg(test)]
166 pub(crate) fn get_button_press_state_count(&self) -> u32 {
167 self.button_press_state.lock().count
168 }
169
170 #[cfg(test)]
171 pub(crate) fn set_button_press_state_count(&self, count: u32) {
172 self.button_press_state.lock().count = count;
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use fidl_fuchsia_ui_input as fui_input;
180
181 struct FakeClock {
182 now: Mutex<i64>,
183 }
184
185 impl FakeClock {
186 fn new() -> Self {
187 Self { now: Mutex::new(0) }
188 }
189
190 fn advance_ns(&self, duration_ns: i64) {
191 *self.now.lock() += duration_ns;
192 }
193 }
194
195 impl Clock for FakeClock {
196 fn now_ns(&self) -> i64 {
197 *self.now.lock()
198 }
199 }
200
201 impl<T: Clock> Clock for Arc<T> {
205 fn now_ns(&self) -> i64 {
206 self.as_ref().now_ns()
207 }
208 }
209
210 #[fuchsia::test]
211 fn test_successful_press_sequence() {
212 let clock = Arc::new(FakeClock::new());
213 let debug_state = DebugState::new_for_test(true, clock.clone());
214 let press_event =
215 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
216
217 for i in 1..REQUIRED_PRESS_COUNT {
219 assert!(
220 !debug_state.process_button_event(&press_event),
221 "Incorrectly triggered action after {i} presses"
222 );
223 let state = debug_state.button_press_state.lock();
224 assert_eq!(
225 state.action_triggered_count, 0,
226 "Incorrectly incremented trigger count after {i} presses. State: {state:?}"
227 );
228 }
229
230 assert!(
231 debug_state.process_button_event(&press_event),
232 "The final press should trigger the sequence."
233 );
234 let state = debug_state.button_press_state.lock();
235 assert_eq!(state.action_triggered_count, 1, "State: {state:?}");
236 }
237
238 #[fuchsia::test]
239 fn test_counter_resets_after_successful_sequence() {
240 let clock = Arc::new(FakeClock::new());
241 let debug_state = DebugState::new_for_test(true, clock.clone());
242 let press_event =
243 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
244
245 for _ in 1..REQUIRED_PRESS_COUNT {
247 assert!(!debug_state.process_button_event(&press_event));
248 let state = debug_state.button_press_state.lock();
249 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
250 }
251 assert!(debug_state.process_button_event(&press_event));
252 {
253 let state = debug_state.button_press_state.lock();
254 assert_eq!(state.action_triggered_count, 1, "State: {state:?}");
255 }
256
257 assert!(
258 !debug_state.process_button_event(&press_event),
259 "The next press should start a new sequence."
260 );
261 let state = debug_state.button_press_state.lock();
262 assert_eq!(state.count, 1, "State: {state:?}");
263 assert_eq!(state.action_triggered_count, 1, "State: {state:?}");
264 }
265
266 #[test]
267 fn test_counter_resets_after_timeout() {
268 let clock = Arc::new(FakeClock::new());
269 let debug_state = DebugState::new_for_test(true, clock.clone());
270 let press_event =
271 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
272
273 for _ in 1..REQUIRED_PRESS_COUNT - 1 {
275 assert!(!debug_state.process_button_event(&press_event));
276 let state = debug_state.button_press_state.lock();
277 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
278 }
279 clock.advance_ns(*MAX_PRESS_INTERVAL_NS + 1);
281
282 assert!(
283 !debug_state.process_button_event(&press_event),
284 "This press should reset the counter to 1, not increment it."
285 );
286 let state = debug_state.button_press_state.lock();
287 assert_eq!(state.count, 1, "State: {state:?}");
288 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
289 }
290
291 #[fuchsia::test]
292 fn test_power_button_press_resets_counter() {
293 let clock = Arc::new(FakeClock::new());
294 let debug_state = DebugState::new_for_test(true, clock.clone());
295 let press_event =
296 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
297 let power_press_event =
298 fui_input::MediaButtonsEvent { power: Some(true), ..Default::default() };
299
300 assert!(
301 !debug_state.process_button_event(&press_event),
302 "first function press should not trigger action"
303 );
304 {
305 let state = debug_state.button_press_state.lock();
306 assert_eq!(state.count, 1, "should have count of 1. State: {state:?}");
307 }
308 assert!(
309 !debug_state.process_button_event(&power_press_event),
310 "power press should not trigger action"
311 );
312 {
313 let state = debug_state.button_press_state.lock();
314 assert_eq!(
315 state.count, 0,
316 "A non-function-button press should reset the counter. State: {state:?}"
317 );
318 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
319 }
320
321 assert!(
322 !debug_state.process_button_event(&press_event),
323 "The next press should start a new sequence."
324 );
325 let state = debug_state.button_press_state.lock();
326 assert_eq!(state.count, 1, "State: {state:?}");
327 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
328 }
329
330 #[fuchsia::test]
331 fn test_power_button_release_resets_counter() {
332 let clock = Arc::new(FakeClock::new());
333 let debug_state = DebugState::new_for_test(true, clock.clone());
334 let function_press_event =
335 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
336 let function_release_event =
337 fui_input::MediaButtonsEvent { function: Some(false), ..Default::default() };
338 let power_press_event =
339 fui_input::MediaButtonsEvent { power: Some(true), ..Default::default() };
340 let power_release_event =
341 fui_input::MediaButtonsEvent { power: Some(false), ..Default::default() };
342
343 assert!(
344 !debug_state.process_button_event(&power_press_event),
345 "power press should not trigger action"
346 );
347 {
348 let state = debug_state.button_press_state.lock();
349 assert_eq!(state.count, 0, "count should be 0 after power press. State: {state:?}");
350 }
351 assert!(
352 !debug_state.process_button_event(&function_press_event),
353 "function press should not trigger action"
354 );
355 {
356 let state = debug_state.button_press_state.lock();
357 assert_eq!(state.count, 1, "count should be 1 after function press. State: {state:?}");
358 }
359 assert!(
360 !debug_state.process_button_event(&power_release_event),
361 "A non-function-button release should reset the counter."
362 );
363 {
364 let state = debug_state.button_press_state.lock();
365 assert_eq!(state.count, 0, "count should be 0 after power release. State: {state:?}");
366 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
367 }
368 assert!(
369 !debug_state.process_button_event(&function_release_event),
370 "function release should not trigger action"
371 );
372 {
373 let state = debug_state.button_press_state.lock();
374 assert_eq!(
375 state.count, 0,
376 "count should be 0 after function release. State: {state:?}"
377 );
378 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
379 }
380
381 assert!(
382 !debug_state.process_button_event(&function_press_event),
383 "The next press should start a new sequence."
384 );
385 let state = debug_state.button_press_state.lock();
386 assert_eq!(state.count, 1, "State: {state:?}");
387 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
388 }
389
390 #[fuchsia::test]
391 fn test_ignores_function_button_releases() {
392 let clock = Arc::new(FakeClock::new());
393 let debug_state = DebugState::new_for_test(true, clock.clone());
394 let press_event =
395 fui_input::MediaButtonsEvent { function: Some(true), ..Default::default() };
396 let function_release_event =
397 fui_input::MediaButtonsEvent { function: Some(false), ..Default::default() };
398
399 assert!(
400 !debug_state.process_button_event(&press_event),
401 "first function press should not trigger action"
402 );
403 {
404 let state = debug_state.button_press_state.lock();
405 assert_eq!(state.count, 1, "count should be 1. State: {state:?}");
406 }
407 assert!(
408 !debug_state.process_button_event(&function_release_event),
409 "A button release should be ignored and not affect the counter."
410 );
411 {
412 let state = debug_state.button_press_state.lock();
413 assert_eq!(state.count, 1, "count should still be 1. State: {state:?}");
414 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
415 }
416 assert!(
417 !debug_state.process_button_event(&press_event),
418 "second function press should not trigger action"
419 );
420 let state = debug_state.button_press_state.lock();
421 assert_eq!(state.count, 2, "count should be 2. State: {state:?}");
422 assert_eq!(state.action_triggered_count, 0, "State: {state:?}");
423 }
424}