Skip to main content

fxfs_platform_testing/fuchsia/
power.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 fidl_fuchsia_power_battery::{
6    BatteryInfoWatcherMarker, BatteryInfoWatcherRequest, BatteryManagerMarker, BatteryManagerProxy,
7    ChargeSource,
8};
9use fidl_fuchsia_power_system as _;
10use fuchsia_component_client::connect_to_protocol;
11use futures::stream::{BoxStream, StreamExt};
12use fxfs::filesystem::{PowerManager, WakeLease};
13use fxfs::log::*;
14use std::sync::Arc;
15
16pub struct FuchsiaPowerManager {}
17
18impl FuchsiaPowerManager {
19    pub fn new() -> Arc<Self> {
20        Arc::new(Self {})
21    }
22
23    fn watch_battery_with_proxy(
24        proxy: BatteryManagerProxy,
25    ) -> BoxStream<'static, (bool, Option<WakeLease>)> {
26        let (watcher_client, watcher_stream) =
27            fidl::endpoints::create_request_stream::<BatteryInfoWatcherMarker>();
28
29        if let Err(error) = proxy.watch(watcher_client) {
30            error!(error:?; "Failed to register battery watcher");
31            return futures::stream::empty().boxed();
32        }
33
34        futures::stream::unfold(watcher_stream, |mut stream| async move {
35            match stream.next().await {
36                Some(Ok(BatteryInfoWatcherRequest::OnChangeBatteryInfo {
37                    info,
38                    wake_lease,
39                    responder,
40                })) => {
41                    let _ = responder.send();
42                    // We check charge_source rather than charge_status because charge_status
43                    // might be FULL even when the device has just been taken off charge.
44                    // charge_source will indicate the actual source of power for the system.
45                    let on_battery = !matches!(
46                        info.charge_source,
47                        Some(ChargeSource::AcAdapter)
48                            | Some(ChargeSource::Usb)
49                            | Some(ChargeSource::Wireless)
50                    );
51                    let lease = wake_lease.map(WakeLease::new);
52                    Some(((on_battery, lease), stream))
53                }
54                Some(Err(error)) => {
55                    error!(error:?; "Battery watcher stream error");
56                    None
57                }
58                None => None,
59            }
60        })
61        .boxed()
62    }
63}
64
65impl PowerManager for FuchsiaPowerManager {
66    fn watch_battery(self: Arc<Self>) -> BoxStream<'static, (bool, Option<WakeLease>)> {
67        match connect_to_protocol::<BatteryManagerMarker>() {
68            Ok(proxy) => Self::watch_battery_with_proxy(proxy),
69            Err(error) => {
70                warn!(error:?; "Failed to connect to BatteryManager");
71                futures::stream::empty().boxed()
72            }
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use fidl_fuchsia_power_battery::{BatteryInfo, BatteryManagerRequest};
81
82    #[fuchsia::test]
83    async fn test_watch_battery_charge_sources_and_lease() {
84        let _manager = FuchsiaPowerManager::new();
85        let (proxy, mut manager_stream) =
86            fidl::endpoints::create_proxy_and_stream::<BatteryManagerMarker>();
87
88        let mut battery_stream = FuchsiaPowerManager::watch_battery_with_proxy(proxy);
89
90        let watcher_proxy = match manager_stream.next().await {
91            Some(Ok(BatteryManagerRequest::Watch { watcher, .. })) => watcher.into_proxy(),
92            other => panic!("Unexpected request: {:?}", other),
93        };
94
95        let server_task = fuchsia_async::Task::spawn(async move {
96            // AC adapter -> on_battery = false
97            watcher_proxy
98                .on_change_battery_info(
99                    &BatteryInfo {
100                        charge_source: Some(ChargeSource::AcAdapter),
101                        ..Default::default()
102                    },
103                    None,
104                )
105                .await
106                .unwrap();
107
108            // USB -> on_battery = false, with a wake lease
109            let (ep1, _ep2) = zx::EventPair::create();
110            watcher_proxy
111                .on_change_battery_info(
112                    &BatteryInfo { charge_source: Some(ChargeSource::Usb), ..Default::default() },
113                    Some(ep1),
114                )
115                .await
116                .unwrap();
117
118            // Wireless -> on_battery = false
119            watcher_proxy
120                .on_change_battery_info(
121                    &BatteryInfo {
122                        charge_source: Some(ChargeSource::Wireless),
123                        ..Default::default()
124                    },
125                    None,
126                )
127                .await
128                .unwrap();
129
130            // ChargeSource::None -> on_battery = true
131            watcher_proxy
132                .on_change_battery_info(
133                    &BatteryInfo { charge_source: Some(ChargeSource::None), ..Default::default() },
134                    None,
135                )
136                .await
137                .unwrap();
138
139            // None -> on_battery = true
140            watcher_proxy.on_change_battery_info(&BatteryInfo::default(), None).await.unwrap();
141        });
142
143        let (on_battery, lease) = battery_stream.next().await.unwrap();
144        assert!(!on_battery);
145        assert!(lease.is_none());
146
147        let (on_battery, lease) = battery_stream.next().await.unwrap();
148        assert!(!on_battery);
149        assert!(lease.is_some());
150
151        let (on_battery, lease) = battery_stream.next().await.unwrap();
152        assert!(!on_battery);
153        assert!(lease.is_none());
154
155        let (on_battery, lease) = battery_stream.next().await.unwrap();
156        assert!(on_battery);
157        assert!(lease.is_none());
158
159        let (on_battery, lease) = battery_stream.next().await.unwrap();
160        assert!(on_battery);
161        assert!(lease.is_none());
162
163        server_task.await;
164        assert!(battery_stream.next().await.is_none());
165    }
166
167    #[fuchsia::test]
168    async fn test_watch_battery_disconnected_proxy() {
169        let (proxy, manager_stream) =
170            fidl::endpoints::create_proxy_and_stream::<BatteryManagerMarker>();
171        drop(manager_stream);
172
173        let mut battery_stream = FuchsiaPowerManager::watch_battery_with_proxy(proxy);
174        assert!(battery_stream.next().await.is_none());
175    }
176}