1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use anyhow::{anyhow, Context};
use fidl::endpoints::{ClientEnd, Proxy};
use fidl_fuchsia_power_broker as fbroker;
use fidl_fuchsia_power_system as fsystem;
use rand::{distributions::Alphanumeric, Rng};

/// A power element representing the session.
///
/// This power element is owned and registered by `session_manager`. This power element is
/// added in the power topology as a dependent on the Application Activity element that is
/// owned by the SAG.
///
/// After `session_manager` starts, a power-on lease will be created and retained.
/// The session component may fetch the lease from `session_manager` and decide when to
/// drop it.
///
/// When stopping or restarting the session, the power element and the power-on lease will
/// be recreated, returning thing to the initial started state.
pub struct PowerElement {
    // Keeps the element alive.
    #[allow(dead_code)]
    power_element: ClientEnd<fbroker::ElementControlMarker>,

    /// The first lease on the power element.
    lease: Option<ClientEnd<fbroker::LeaseControlMarker>>,
}

/// The power levels defined for the session manager power element.
///
/// | Power Mode        | Level |
/// | ----------------- | ----- |
/// | On                | 1     |
/// | Off               | 0     |
///
static POWER_ON_LEVEL: fbroker::PowerLevel = 1;

impl PowerElement {
    pub async fn new() -> Result<Self, anyhow::Error> {
        let topology = fuchsia_component::client::connect_to_protocol::<fbroker::TopologyMarker>()?;
        let activity_governor =
            fuchsia_component::client::connect_to_protocol::<fsystem::ActivityGovernorMarker>()?;

        // Create the PowerMode power element depending on the Execution State of SAG.
        let power_elements = activity_governor
            .get_power_elements()
            .await
            .context("cannot get power elements from SAG")?;
        let Some(Some(application_activity_token)) = power_elements
            .application_activity
            .map(|application_activity| application_activity.active_dependency_token)
        else {
            return Err(anyhow!("Did not find application activity active dependency token"));
        };

        // TODO(https://fxbug.dev/316023943): also depend on execution_resume_latency after implemented.
        let power_levels: Vec<u8> = (0..=POWER_ON_LEVEL).collect();
        let (lessor, lessor_server_end) = fidl::endpoints::create_proxy::<fbroker::LessorMarker>()?;
        let random_string: String =
            rand::thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect();
        let power_element = topology
            .add_element(fbroker::ElementSchema {
                element_name: Some(format!("session-manager-element-{}", random_string)),
                initial_current_level: Some(POWER_ON_LEVEL),
                valid_levels: Some(power_levels),
                dependencies: Some(vec![fbroker::LevelDependency {
                    dependency_type: fbroker::DependencyType::Active,
                    dependent_level: POWER_ON_LEVEL,
                    requires_token: application_activity_token,
                    requires_level: fsystem::ApplicationActivityLevel::Active.into_primitive(),
                }]),
                lessor_channel: Some(lessor_server_end),
                ..Default::default()
            })
            .await?
            .map_err(|e| anyhow!("PowerBroker::AddElementError({e:?})"))?;

        // Power on by holding a lease.
        let lease = lessor
            .lease(POWER_ON_LEVEL)
            .await?
            .map_err(|e| anyhow!("PowerBroker::LeaseError({e:?})"))?;

        // Wait for the lease to be satisfied.
        let lease = lease.into_proxy()?;
        let mut status = fbroker::LeaseStatus::Unknown;
        loop {
            match lease.watch_status(status).await? {
                fbroker::LeaseStatus::Satisfied => break,
                new_status @ _ => status = new_status,
            }
        }
        let lease = lease.into_client_end().expect("No ongoing calls on lease");

        Ok(Self { power_element, lease: Some(lease) })
    }

    pub fn take_lease(&mut self) -> Option<ClientEnd<fbroker::LeaseControlMarker>> {
        self.lease.take()
    }

    pub fn has_lease(&self) -> bool {
        self.lease.is_some()
    }
}