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#[cfg_attr(test, derive(Debug))]
16pub struct WakeLease {
17    _token: fsystem::LeaseToken,
18}
19
20impl WakeLease {
21    /// Constructs a WakeLease from an underlying token.
22    /// This is intended strictly for use in test environments.
23    #[doc(hidden)]
24    pub fn from_token_for_test(token: fsystem::LeaseToken) -> Self {
25        Self { _token: token }
26    }
27}
28
29#[async_trait]
30pub trait PowerManager: Send + Sync {
31    async fn take_wake_lease(&self, name: &str) -> Option<WakeLease>;
32    async fn power_element_lease(
33        &self,
34        lease_name: &str,
35        dependency_token: fbroker::DependencyToken,
36        dependency_level: u8,
37    ) -> Result<fbroker::LeaseToken, Error>;
38    async fn register_suspend_blocker(
39        &self,
40        suspend_blocker: fidl::endpoints::ClientEnd<fsystem::SuspendBlockerMarker>,
41        name: &str,
42    ) -> Result<WakeLease, Error>;
43}
44
45pub struct DevicePowerManager {
46    proxies: Option<(fsystem::ActivityGovernorProxy, fbroker::TopologyProxy)>,
47}
48
49impl DevicePowerManager {
50    pub fn new(proxies: Option<(fsystem::ActivityGovernorProxy, fbroker::TopologyProxy)>) -> Self {
51        Self { proxies }
52    }
53}
54
55#[async_trait]
56impl PowerManager for DevicePowerManager {
57    async fn take_wake_lease(&self, name: &str) -> Option<WakeLease> {
58        let (activity_governor, _) = self.proxies.as_ref()?;
59        match activity_governor.acquire_wake_lease(name).await {
60            Ok(Ok(token)) => Some(WakeLease { _token: token }),
61            Ok(Err(e)) => {
62                warn!("Failed to acquire wake lease {}: {:?}", name, e);
63                None
64            }
65            Err(e) => {
66                warn!("FIDL error when acquiring wake lease {}: {:?}", name, e);
67                None
68            }
69        }
70    }
71
72    async fn power_element_lease(
73        &self,
74        lease_name: &str,
75        dependency_token: fbroker::DependencyToken,
76        dependency_level: u8,
77    ) -> Result<fbroker::LeaseToken, Error> {
78        let (_, power_broker) =
79            self.proxies.as_ref().ok_or_else(|| anyhow::anyhow!("Power broker not available"))?;
80
81        let dep = fbroker::LeaseDependency {
82            requires_token: Some(dependency_token),
83            requires_level: Some(dependency_level),
84            ..Default::default()
85        };
86        let (lease_token_local, lease_token_server) = zx::EventPair::create();
87        let schema = fbroker::LeaseSchema {
88            lease_token: Some(lease_token_server),
89            lease_name: Some(lease_name.to_string()),
90            dependencies: Some(vec![dep]),
91            ..Default::default()
92        };
93        power_broker
94            .lease(schema)
95            .await?
96            .map_err(|e| anyhow::anyhow!("Power broker returned error: {:?}", e))?;
97        Ok(lease_token_local)
98    }
99
100    async fn register_suspend_blocker(
101        &self,
102        suspend_blocker: fidl::endpoints::ClientEnd<fsystem::SuspendBlockerMarker>,
103        name: &str,
104    ) -> Result<WakeLease, Error> {
105        let (activity_governor, _) = self
106            .proxies
107            .as_ref()
108            .ok_or_else(|| anyhow::anyhow!("Activity governor not available"))?;
109        let payload = fsystem::ActivityGovernorRegisterSuspendBlockerRequest {
110            suspend_blocker: Some(suspend_blocker),
111            name: Some(name.to_string()),
112            ..Default::default()
113        };
114        match activity_governor.register_suspend_blocker(payload).await {
115            Ok(Ok(token)) => Ok(WakeLease { _token: token }),
116            Ok(Err(e)) => Err(anyhow::anyhow!("RegisterSuspendBlocker error: {:?}", e)),
117            Err(e) => Err(anyhow::anyhow!("FIDL error: {:?}", e)),
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use assert_matches::assert_matches;
126    use fuchsia_async as fasync;
127    use futures::StreamExt;
128
129    #[fasync::run_singlethreaded(test)]
130    async fn test_power_manager_disabled_take_wake_lease() {
131        let pm = DevicePowerManager::new(None);
132        assert!(pm.take_wake_lease("test-lease").await.is_none());
133    }
134
135    #[fasync::run_singlethreaded(test)]
136    async fn test_power_manager_disabled_power_element_lease() {
137        let pm = DevicePowerManager::new(None);
138        let token = zx::Event::create();
139        assert!(pm.power_element_lease("test-lease", token, 1).await.is_err());
140    }
141
142    #[fasync::run_singlethreaded(test)]
143    async fn test_power_manager_disabled_register_suspend_blocker() {
144        let pm = DevicePowerManager::new(None);
145        let (client, _) = fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
146        assert!(pm.register_suspend_blocker(client, "test-blocker").await.is_err());
147    }
148
149    #[fasync::run_singlethreaded(test)]
150    async fn test_power_manager_take_wake_lease_success() {
151        let (proxy, mut stream) =
152            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
153        let (topology_proxy, _topology_server) =
154            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
155        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
156
157        let server_fut = async move {
158            while let Some(Ok(req)) = stream.next().await {
159                match req {
160                    fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
161                        assert_eq!(name, "test-wake-lease");
162                        let (_server_token, client_token) = fsystem::LeaseToken::create();
163                        let _ = responder.send(Ok(client_token));
164                    }
165                    other => panic!("Unexpected request: {:?}", other),
166                }
167            }
168        };
169        let client_fut = async move {
170            let lease = pm.take_wake_lease("test-wake-lease").await;
171            assert!(lease.is_some());
172        };
173        futures::join!(server_fut, client_fut);
174    }
175
176    #[fasync::run_singlethreaded(test)]
177    async fn test_power_manager_take_wake_lease_error() {
178        let (proxy, mut stream) =
179            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
180        let (topology_proxy, _topology_server) =
181            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
182        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
183
184        let server_fut = async move {
185            while let Some(Ok(req)) = stream.next().await {
186                match req {
187                    fsystem::ActivityGovernorRequest::AcquireWakeLease { name, responder } => {
188                        assert_eq!(name, "test-wake-lease");
189                        let _ = responder.send(Err(fsystem::AcquireWakeLeaseError::Internal));
190                    }
191                    other => panic!("Unexpected request: {:?}", other),
192                }
193            }
194        };
195        let client_fut = async move {
196            let lease = pm.take_wake_lease("test-wake-lease").await;
197            assert!(lease.is_none());
198        };
199        futures::join!(server_fut, client_fut);
200    }
201
202    #[fasync::run_singlethreaded(test)]
203    async fn test_power_manager_register_suspend_blocker_success() {
204        let (proxy, mut stream) =
205            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
206        let (topology_proxy, _topology_server) =
207            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
208        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
209
210        let server_fut = async move {
211            while let Some(Ok(req)) = stream.next().await {
212                match req {
213                    fsystem::ActivityGovernorRequest::RegisterSuspendBlocker {
214                        payload,
215                        responder,
216                    } => {
217                        assert_eq!(payload.name.as_deref(), Some("test-blocker"));
218                        let (_server_token, client_token) = fsystem::LeaseToken::create();
219                        let _ = responder.send(Ok(client_token));
220                    }
221                    other => panic!("Unexpected request: {:?}", other),
222                }
223            }
224        };
225        let client_fut = async move {
226            let (client, _server) =
227                fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
228            let lease = pm.register_suspend_blocker(client, "test-blocker").await;
229            assert_matches!(lease, Ok(_));
230        };
231        futures::join!(server_fut, client_fut);
232    }
233
234    #[fasync::run_singlethreaded(test)]
235    async fn test_power_manager_register_suspend_blocker_error() {
236        let (proxy, mut stream) =
237            fidl::endpoints::create_proxy_and_stream::<fsystem::ActivityGovernorMarker>();
238        let (topology_proxy, _topology_server) =
239            fidl::endpoints::create_proxy::<fbroker::TopologyMarker>();
240        let pm = DevicePowerManager::new(Some((proxy, topology_proxy)));
241
242        let server_fut = async move {
243            while let Some(Ok(req)) = stream.next().await {
244                match req {
245                    fsystem::ActivityGovernorRequest::RegisterSuspendBlocker {
246                        payload,
247                        responder,
248                    } => {
249                        assert_eq!(payload.name.as_deref(), Some("test-blocker"));
250                        let _ = responder.send(Err(fsystem::RegisterSuspendBlockerError::Internal));
251                    }
252                    other => panic!("Unexpected request: {:?}", other),
253                }
254            }
255        };
256        let client_fut = async move {
257            let (client, _server) =
258                fidl::endpoints::create_endpoints::<fsystem::SuspendBlockerMarker>();
259            let lease = pm.register_suspend_blocker(client, "test-blocker").await;
260            assert_matches!(lease, Err(_));
261        };
262        futures::join!(server_fut, client_fut);
263    }
264
265    #[fasync::run_singlethreaded(test)]
266    async fn test_power_manager_power_element_lease_success() {
267        let (ag_proxy, _ag_server) =
268            fidl::endpoints::create_proxy::<fsystem::ActivityGovernorMarker>();
269        let (topology_proxy, mut stream) =
270            fidl::endpoints::create_proxy_and_stream::<fbroker::TopologyMarker>();
271        let pm = DevicePowerManager::new(Some((ag_proxy, topology_proxy)));
272
273        let server_fut = async move {
274            while let Some(Ok(req)) = stream.next().await {
275                match req {
276                    fbroker::TopologyRequest::Lease { responder, .. } => {
277                        let _ = responder.send(Ok(()));
278                    }
279                    other => panic!("Unexpected request: {:?}", other),
280                }
281            }
282        };
283        let client_fut = async move {
284            let token = zx::Event::create();
285            let lease = pm.power_element_lease("test-lease", token, 1).await;
286            assert_matches!(lease, Ok(_));
287        };
288        futures::join!(server_fut, client_fut);
289    }
290
291    #[fasync::run_singlethreaded(test)]
292    async fn test_power_manager_power_element_lease_error() {
293        let (ag_proxy, _ag_server) =
294            fidl::endpoints::create_proxy::<fsystem::ActivityGovernorMarker>();
295        let (topology_proxy, mut stream) =
296            fidl::endpoints::create_proxy_and_stream::<fbroker::TopologyMarker>();
297        let pm = DevicePowerManager::new(Some((ag_proxy, topology_proxy)));
298
299        let server_fut = async move {
300            while let Some(Ok(req)) = stream.next().await {
301                match req {
302                    fbroker::TopologyRequest::Lease { responder, .. } => {
303                        let _ = responder.send(Err(fbroker::LeaseError::Internal));
304                    }
305                    other => panic!("Unexpected request: {:?}", other),
306                }
307            }
308        };
309        let client_fut = async move {
310            let token = zx::Event::create();
311            let lease = pm.power_element_lease("test-lease", token, 1).await;
312            assert_matches!(lease, Err(_));
313        };
314        futures::join!(server_fut, client_fut);
315    }
316}