Skip to main content

wlan_power_manager/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Error;
6use async_trait::async_trait;
7use fidl_fuchsia_power_broker as fbroker;
8use fidl_fuchsia_power_system as fsystem;
9use log::warn;
10
11pub const POWER_LEVEL_OFF: u8 = 0;
12pub const POWER_LEVEL_SUSPEND: u8 = 1;
13pub const POWER_LEVEL_ACTIVE: u8 = 2;
14
15#[derive(Debug)]
16pub struct WakeLease {
17    _token: fsystem::LeaseToken,
18}
19
20impl WakeLease {
21    pub fn from_token(token: fsystem::LeaseToken) -> Self {
22        Self { _token: token }
23    }
24
25    /// Constructs a WakeLease from an underlying token.
26    /// This is intended strictly for use in test environments.
27    #[doc(hidden)]
28    pub fn from_token_for_test(token: fsystem::LeaseToken) -> Self {
29        Self { _token: token }
30    }
31}
32
33pub trait SyncPowerManager: Send + Sync + std::fmt::Debug {
34    fn take_wake_lease(&self, name: &str) -> Option<WakeLease>;
35}
36
37impl SyncPowerManager for () {
38    fn take_wake_lease(&self, _name: &str) -> Option<WakeLease> {
39        None
40    }
41}
42
43const ACQUIRE_WAKE_LEASE_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(2);
44
45#[derive(Debug)]
46pub struct DeviceSyncPowerManager {
47    proxy: Option<fsystem::ActivityGovernorSynchronousProxy>,
48}
49
50impl DeviceSyncPowerManager {
51    pub fn new(proxy: Option<fsystem::ActivityGovernorSynchronousProxy>) -> Self {
52        Self { proxy }
53    }
54}
55
56impl SyncPowerManager for DeviceSyncPowerManager {
57    fn take_wake_lease(&self, name: &str) -> Option<WakeLease> {
58        let activity_governor = self.proxy.as_ref()?;
59        let deadline = zx::MonotonicInstant::after(ACQUIRE_WAKE_LEASE_TIMEOUT);
60        match activity_governor.acquire_wake_lease(name, deadline) {
61            Ok(Ok(token)) => Some(WakeLease { _token: token }),
62            Ok(Err(e)) => {
63                warn!("Failed to acquire wake lease {}: {:?}", name, e);
64                None
65            }
66            Err(e) => {
67                warn!("FIDL error when acquiring wake lease {}: {:?}", name, e);
68                None
69            }
70        }
71    }
72}
73
74#[async_trait]
75pub trait PowerManager: Send + Sync {
76    async fn take_wake_lease(&self, name: &str) -> Option<WakeLease>;
77    async fn power_element_lease(
78        &self,
79        lease_name: &str,
80        dependency_token: fbroker::DependencyToken,
81        dependency_level: u8,
82    ) -> Result<fbroker::LeaseToken, Error>;
83    async fn register_suspend_blocker(
84        &self,
85        suspend_blocker: fidl::endpoints::ClientEnd<fsystem::SuspendBlockerMarker>,
86        name: &str,
87    ) -> Result<WakeLease, Error>;
88}
89
90pub struct DevicePowerManager {
91    proxies: Option<(fsystem::ActivityGovernorProxy, fbroker::TopologyProxy)>,
92}
93
94impl DevicePowerManager {
95    pub fn new(proxies: Option<(fsystem::ActivityGovernorProxy, fbroker::TopologyProxy)>) -> Self {
96        Self { proxies }
97    }
98}
99
100#[async_trait]
101impl PowerManager for DevicePowerManager {
102    async fn take_wake_lease(&self, name: &str) -> Option<WakeLease> {
103        let (activity_governor, _) = self.proxies.as_ref()?;
104        match activity_governor.acquire_wake_lease(name).await {
105            Ok(Ok(token)) => Some(WakeLease { _token: token }),
106            Ok(Err(e)) => {
107                warn!("Failed to acquire wake lease {}: {:?}", name, e);
108                None
109            }
110            Err(e) => {
111                warn!("FIDL error when acquiring wake lease {}: {:?}", name, e);
112                None
113            }
114        }
115    }
116
117    async fn power_element_lease(
118        &self,
119        lease_name: &str,
120        dependency_token: fbroker::DependencyToken,
121        dependency_level: u8,
122    ) -> Result<fbroker::LeaseToken, Error> {
123        let (_, power_broker) =
124            self.proxies.as_ref().ok_or_else(|| anyhow::anyhow!("Power broker not available"))?;
125
126        let dep = fbroker::LeaseDependency {
127            requires_token: Some(dependency_token),
128            requires_level: Some(dependency_level),
129            ..Default::default()
130        };
131        let (lease_token_local, lease_token_server) = zx::EventPair::create();
132        let schema = fbroker::LeaseSchema {
133            lease_token: Some(lease_token_server),
134            lease_name: Some(lease_name.to_string()),
135            dependencies: Some(vec![dep]),
136            ..Default::default()
137        };
138        power_broker
139            .lease(schema)
140            .await?
141            .map_err(|e| anyhow::anyhow!("Power broker returned error: {:?}", e))?;
142        Ok(lease_token_local)
143    }
144
145    async fn register_suspend_blocker(
146        &self,
147        suspend_blocker: fidl::endpoints::ClientEnd<fsystem::SuspendBlockerMarker>,
148        name: &str,
149    ) -> Result<WakeLease, Error> {
150        let (activity_governor, _) = self
151            .proxies
152            .as_ref()
153            .ok_or_else(|| anyhow::anyhow!("Activity governor not available"))?;
154        let payload = fsystem::ActivityGovernorRegisterSuspendBlockerRequest {
155            suspend_blocker: Some(suspend_blocker),
156            name: Some(name.to_string()),
157            ..Default::default()
158        };
159        match activity_governor.register_suspend_blocker(payload).await {
160            Ok(Ok(token)) => Ok(WakeLease { _token: token }),
161            Ok(Err(e)) => Err(anyhow::anyhow!("RegisterSuspendBlocker error: {:?}", e)),
162            Err(e) => Err(anyhow::anyhow!("FIDL error: {:?}", e)),
163        }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use assert_matches::assert_matches;
171    use fuchsia_async as fasync;
172    use futures::StreamExt;
173
174    #[fasync::run_singlethreaded(test)]
175    async fn test_power_manager_disabled_take_wake_lease() {
176        let pm = DevicePowerManager::new(None);
177        assert!(pm.take_wake_lease("test-lease").await.is_none());
178    }
179
180    #[fasync::run_singlethreaded(test)]
181    async fn test_power_manager_disabled_power_element_lease() {
182        let pm = DevicePowerManager::new(None);
183        let token = zx::Event::create();
184        assert!(pm.power_element_lease("test-lease", token, 1).await.is_err());
185    }
186
187    #[fasync::run_singlethreaded(test)]
188    async fn test_power_manager_disabled_register_suspend_blocker() {
189        let pm = DevicePowerManager::new(None);
190        let (client, _) = fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
191        assert!(pm.register_suspend_blocker(client, "test-blocker").await.is_err());
192    }
193
194    #[fasync::run_singlethreaded(test)]
195    async fn test_power_manager_take_wake_lease_success() {
196        let (proxy, mut stream) =
197            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
198        let (topology_proxy, _topology_server) =
199            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
200        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
201
202        let server_fut = async move {
203            while let Some(Ok(req)) = stream.next().await {
204                match req {
205                    fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
206                        assert_eq!(name, "test-wake-lease");
207                        let (_server_token, client_token) = fsystem::LeaseToken::create();
208                        let _ = responder.send(Ok(client_token));
209                    }
210                    other => panic!("Unexpected request: {:?}", other),
211                }
212            }
213        };
214        let client_fut = async move {
215            let lease = pm.take_wake_lease("test-wake-lease").await;
216            assert!(lease.is_some());
217        };
218        futures::join!(server_fut, client_fut);
219    }
220
221    #[fasync::run_singlethreaded(test)]
222    async fn test_power_manager_take_wake_lease_error() {
223        let (proxy, mut stream) =
224            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
225        let (topology_proxy, _topology_server) =
226            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
227        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
228
229        let server_fut = async move {
230            while let Some(Ok(req)) = stream.next().await {
231                match req {
232                    fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
233                        assert_eq!(name, "test-wake-lease");
234                        let _ = responder.send(Err(fsystem::AcquireWakeLeaseError::Internal));
235                    }
236                    other => panic!("Unexpected request: {:?}", other),
237                }
238            }
239        };
240        let client_fut = async move {
241            let lease = pm.take_wake_lease("test-wake-lease").await;
242            assert!(lease.is_none());
243        };
244        futures::join!(server_fut, client_fut);
245    }
246
247    #[fasync::run_singlethreaded(test)]
248    async fn test_power_manager_register_suspend_blocker_success() {
249        let (proxy, mut stream) =
250            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
251        let (topology_proxy, _topology_server) =
252            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
253        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
254
255        let server_fut = async move {
256            while let Some(Ok(req)) = stream.next().await {
257                match req {
258                    fsystem::ActivityGovernorRequest::RegisterSuspendBlocker {
259                        payload,
260                        responder,
261                    } => {
262                        assert_eq!(payload.name.as_deref(), Some("test-blocker"));
263                        let (_server_token, client_token) = fsystem::LeaseToken::create();
264                        let _ = responder.send(Ok(client_token));
265                    }
266                    other => panic!("Unexpected request: {:?}", other),
267                }
268            }
269        };
270        let client_fut = async move {
271            let (client, _server) =
272                fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
273            let lease = pm.register_suspend_blocker(client, "test-blocker").await;
274            assert_matches!(lease, Ok(_));
275        };
276        futures::join!(server_fut, client_fut);
277    }
278
279    #[fasync::run_singlethreaded(test)]
280    async fn test_power_manager_register_suspend_blocker_error() {
281        let (proxy, mut stream) =
282            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
283        let (topology_proxy, _topology_server) =
284            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
285        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
286
287        let server_fut = async move {
288            while let Some(Ok(req)) = stream.next().await {
289                match req {
290                    fsystem::ActivityGovernorRequest::RegisterSuspendBlocker {
291                        payload,
292                        responder,
293                    } => {
294                        assert_eq!(payload.name.as_deref(), Some("test-blocker"));
295                        let _ = responder.send(Err(fsystem::RegisterSuspendBlockerError::Internal));
296                    }
297                    other => panic!("Unexpected request: {:?}", other),
298                }
299            }
300        };
301        let client_fut = async move {
302            let (client, _server) =
303                fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
304            let lease = pm.register_suspend_blocker(client, "test-blocker").await;
305            assert_matches!(lease, Err(_));
306        };
307        futures::join!(server_fut, client_fut);
308    }
309
310    #[fasync::run_singlethreaded(test)]
311    async fn test_power_manager_power_element_lease_success() {
312        let (ag_proxy, _ag_server) =
313            fidl::endpoints::create_proxy::<fsystem::ActivityGovernorMarker>();
314        let (topology_proxy, mut stream) =
315            fidl::endpoints::create_proxy_and_stream::<fbroker::TopologyMarker>();
316        let pm = DevicePowerManager::new(Some((ag_proxy, topology_proxy)));
317
318        let server_fut = async move {
319            while let Some(Ok(req)) = stream.next().await {
320                match req {
321                    fbroker::TopologyRequest::Lease { responder, .. } => {
322                        let _ = responder.send(Ok(()));
323                    }
324                    other => panic!("Unexpected request: {:?}", other),
325                }
326            }
327        };
328        let client_fut = async move {
329            let token = zx::Event::create();
330            let lease = pm.power_element_lease("test-lease", token, 1).await;
331            assert_matches!(lease, Ok(_));
332        };
333        futures::join!(server_fut, client_fut);
334    }
335
336    #[fasync::run_singlethreaded(test)]
337    async fn test_power_manager_power_element_lease_error() {
338        let (ag_proxy, _ag_server) =
339            fidl::endpoints::create_proxy::<fsystem::ActivityGovernorMarker>();
340        let (topology_proxy, mut stream) =
341            fidl::endpoints::create_proxy_and_stream::<fbroker::TopologyMarker>();
342        let pm = DevicePowerManager::new(Some((ag_proxy, topology_proxy)));
343
344        let server_fut = async move {
345            while let Some(Ok(req)) = stream.next().await {
346                match req {
347                    fbroker::TopologyRequest::Lease { responder, .. } => {
348                        let _ = responder.send(Err(fbroker::LeaseError::Internal));
349                    }
350                    other => panic!("Unexpected request: {:?}", other),
351                }
352            }
353        };
354        let client_fut = async move {
355            let token = zx::Event::create();
356            let lease = pm.power_element_lease("test-lease", token, 1).await;
357            assert_matches!(lease, Err(_));
358        };
359        futures::join!(server_fut, client_fut);
360    }
361
362    #[test]
363    fn test_sync_power_manager_disabled_take_wake_lease() {
364        let pm = DeviceSyncPowerManager::new(None);
365        assert!(pm.take_wake_lease("test-lease").is_none());
366        let unit_pm = ();
367        assert!(unit_pm.take_wake_lease("test-lease").is_none());
368    }
369
370    #[test]
371    fn test_sync_power_manager_take_wake_lease_success() {
372        let (client_end, server_end) =
373            fidl::endpoints::create_endpoints::<fsystem::ActivityGovernorMarker>();
374        let proxy = client_end.into_sync_proxy();
375        let pm = DeviceSyncPowerManager::new(Some(proxy));
376
377        let thread_handle = std::thread::spawn(move || {
378            let mut exec = fasync::LocalExecutor::default();
379            let mut stream = server_end.into_stream();
380            exec.run_singlethreaded(async move {
381                if let Some(Ok(req)) = stream.next().await {
382                    match req {
383                        fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
384                            assert_eq!(name, "test-sync-wake-lease");
385                            let (_server_token, client_token) = fsystem::LeaseToken::create();
386                            let _ = responder.send(Ok(client_token));
387                        }
388                        other => panic!("Unexpected request: {:?}", other),
389                    }
390                }
391            });
392        });
393
394        let lease = pm.take_wake_lease("test-sync-wake-lease");
395        assert!(lease.is_some());
396        thread_handle.join().unwrap();
397    }
398
399    #[test]
400    fn test_sync_power_manager_take_wake_lease_error() {
401        let (client_end, server_end) =
402            fidl::endpoints::create_endpoints::<fsystem::ActivityGovernorMarker>();
403        let proxy = client_end.into_sync_proxy();
404        let pm = DeviceSyncPowerManager::new(Some(proxy));
405
406        let thread_handle = std::thread::spawn(move || {
407            let mut exec = fasync::LocalExecutor::default();
408            let mut stream = server_end.into_stream();
409            exec.run_singlethreaded(async move {
410                if let Some(Ok(req)) = stream.next().await {
411                    match req {
412                        fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
413                            assert_eq!(name, "test-sync-wake-lease");
414                            let _ = responder.send(Err(fsystem::AcquireWakeLeaseError::Internal));
415                        }
416                        other => panic!("Unexpected request: {:?}", other),
417                    }
418                }
419            });
420        });
421
422        let lease = pm.take_wake_lease("test-sync-wake-lease");
423        assert!(lease.is_none());
424        thread_handle.join().unwrap();
425    }
426}