blob: 79134770d857bf7625308532382db9f5dd0fb366 [file] [log] [blame]
// Copyright 2019 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 {
fidl_fuchsia_input as input, fidl_fuchsia_ui_focus as ui_focus,
fidl_fuchsia_ui_shortcut as ui_shortcut, fidl_fuchsia_ui_views as ui_views,
fuchsia_scenic as scenic,
fuchsia_syslog::{fx_log_debug, fx_log_info},
fuchsia_zircon as zx,
fuchsia_zircon::AsHandleRef,
futures::{
lock::{MappedMutexGuard, Mutex, MutexGuard},
stream, StreamExt,
},
std::collections::{HashMap, HashSet},
std::ops::Deref,
std::sync::{Arc, Weak},
std::vec::Vec,
};
/// Describes a shortcut activation listener.
pub struct Subscriber {
pub view_ref: ui_views::ViewRef,
pub listener: fidl_fuchsia_ui_shortcut::ListenerProxy,
}
/// Describes all data related to a single connected client:
/// - shortcut activation listener
/// - shortcuts installed
#[derive(Default)]
pub struct ClientRegistry {
pub subscriber: Option<Subscriber>,
pub shortcuts: Vec<Shortcut>,
}
/// Describes all shortcuts and listeners.
#[derive(Clone)]
pub struct RegistryStore {
inner: Arc<Mutex<RegistryStoreInner>>,
}
#[derive(Default)]
struct RegistryStoreInner {
// Weak ref for ClientRegistry to allow shortcuts to be dropped out
// of collection once client connection is removed.
registries: Vec<Weak<Mutex<ClientRegistry>>>,
// The last received focus chain. `None` if an update was never received
// since startup (rare), or set by `handle_focus_change`.
last_seen_focus_chain: Option<Vec<fidl_fuchsia_ui_views::ViewRef>>,
// Currently focused clients in the FocusChain order, i.e. parents first.
// It is computed eagerly on state changes, such that shortcut matching could
// be done quickly on a trigger key.
//
// The focused_registries is invalidated on any changes to the focus or the
// registry itself and needs to be recomputed when `registries` or
// `last_seen_focus_chain` change.
//
// Use `recompute_focused_registries` to keep the registries up to date.
//
// # Invariant
//
// At any given time, `focused_registries` contains all registries
// whose `ViewRef`s are matching the `last_seen_focus_chain`.
focused_registries: Vec<Weak<Mutex<ClientRegistry>>>,
}
impl RegistryStore {
pub fn new() -> Self {
let inner = Arc::new(Mutex::new(RegistryStoreInner::default()));
RegistryStore { inner }
}
/// Add a new client registry to the store.
/// Newly added client registry is returned.
pub async fn add_new_registry(&self) -> Arc<Mutex<ClientRegistry>> {
self.inner.lock().await.add_new_registry()
}
/// Get all client registries.
/// Returned reference can be used as a `Vec<Weak<Mutex<ClientRegistry>>>`.
/// Returned reference contains `MutexGuard`, and as a result it prevents
/// other uses of client registry store (e.g. adding new, removing) until goes
/// out of scope.
/// Is only used for testing.
#[cfg(test)]
async fn get_registries<'a>(&'a self) -> LockedRegistries<'a> {
LockedRegistries(MutexGuard::map(self.inner.lock().await, |r| &mut r.registries))
}
/// Get client registries for currently focused clients.
/// Returned reference can be used as a `Vec<Weak<Mutex<ClientRegistry>>>`.
/// Returned reference contains `MutexGuard`, and as a result it prevents
/// other uses of client registry store (e.g. adding new, removing) until goes
/// out of scope.
/// Returned reference is ordered by Scenic View hierarchy, parent first.
pub async fn get_focused_registries<'a>(&'a self) -> LockedRegistries<'a> {
LockedRegistries(MutexGuard::map(self.inner.lock().await, |r| &mut r.focused_registries))
}
/// Update client registries to account for a `FocusChain` event.
pub async fn handle_focus_change(&self, focus_chain: &ui_focus::FocusChain) {
let focus_chain = match focus_chain {
ui_focus::FocusChain { focus_chain: Some(focus_chain), .. } => focus_chain,
_ => return,
};
self.inner.lock().await.update_focused_registries(focus_chain).await;
}
/// Recomputes the focused registries based on the current state of the focus chain.
/// Review: pub(crate)?
pub async fn recompute_focused_registries(&self) {
self.inner.lock().await.recompute_focused_registries().await;
}
}
impl RegistryStoreInner {
/// Create and add new client registry of shortcuts to the store.
fn add_new_registry(&mut self) -> Arc<Mutex<ClientRegistry>> {
let registry = Arc::new(Mutex::new(ClientRegistry::default()));
self.registries.push(Arc::downgrade(&registry));
registry
}
async fn recompute_focused_registries(&mut self) {
// A better way would be not to clone the focus chain but the mutability
// of self would need to be rethought for that. Use this approach until
// performance becomes an issue.
let focus_chain = self.last_seen_focus_chain.as_ref().map(|f| clone_focus_chain(f));
if let Some(ref f) = focus_chain {
fx_log_debug!("recompute_focused_registries: focus_chain: {:?}", koids_of(f));
self.update_focused_registries(f).await;
}
}
/// Updates `self.focused_registries` to reflect current focus chain.
async fn update_focused_registries(
&mut self,
focus_chain: &Vec<fidl_fuchsia_ui_views::ViewRef>,
) {
// Iterator over all active registries, i.e. all `Weak` refs upgraded.
let registries_iter = stream::iter(
self.registries
.iter()
.cloned()
.filter_map(|registry_weak: Weak<Mutex<ClientRegistry>>| {
registry_weak.upgrade().map(|registry_arc: Arc<Mutex<ClientRegistry>>| {
(registry_arc, registry_weak.clone())
})
})
.into_iter(),
);
// Acquire Mutex locks and hash all client registries by `Koid`.
let mut registries: HashMap<zx::Koid, Weak<Mutex<ClientRegistry>>> = registries_iter
.filter_map(|(registry_arc, registry_weak)| async move {
if let ClientRegistry { subscriber: Some(Subscriber { view_ref, .. }), .. } =
&*registry_arc.lock().await
{
let koid = match view_ref.reference.as_handle_ref().get_koid() {
Ok(koid) => koid,
// If Koid can't be received, this means view ref is invalid
// and can't be notified.
_ => {
fx_log_info!("Client uses invalid ViewRef: {:?}", view_ref);
return None;
}
};
Some((koid, registry_weak))
} else {
// Client registry has no subscriber set and can't be notified.
fx_log_info!("Client has no listener and view ref set up.");
None
}
})
.collect()
.await;
// Keep the last focus chain so that early shortcut registrations would
// be recomputed correctly.
self.last_seen_focus_chain = Some(clone_focus_chain(focus_chain));
// Only focused shortcut clients are retained, and other view_refs are dropped.
self.focused_registries = focus_chain
.into_iter()
.filter_map(|view_ref| {
view_ref
.reference
.as_handle_ref()
.get_koid()
.ok()
.and_then(|koid| registries.remove(&koid))
})
.collect();
fx_log_debug!(
"update_focused_registries: updated focus chain to: {:?}, num focused registries: {:?}",
&self.last_seen_focus_chain.as_ref().map(|f| koids_of(f)),
&self.focused_registries.len()
);
}
}
/// Holds `MutexGuard` in order to provide exclusive access to vector
/// of client registries.
/// Implements `Deref` and can be used as `Vec<Weak<Mutex<ClientRegistry>>>`.
pub struct LockedRegistries<'a>(
MappedMutexGuard<'a, RegistryStoreInner, Vec<Weak<Mutex<ClientRegistry>>>>,
);
impl<'a> Deref for LockedRegistries<'a> {
type Target = Vec<Weak<Mutex<ClientRegistry>>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Abstraction wrapper for FIDL `fuchsia.ui.shortcut.Shortcut`.
/// Implements `Deref` and can be used as `fidl_fuchsia_ui_shortcut::Shortcut`.
#[derive(Debug)]
pub struct Shortcut {
inner: ui_shortcut::Shortcut,
/// The keys that must be armed (already actuated) before this shortcut may
/// be triggered.
pub required_armed_keys: Option<HashSet<input::Key>>,
}
impl Shortcut {
pub fn new(shortcut: ui_shortcut::Shortcut) -> Self {
let required_armed_keys: Option<HashSet<_>> = shortcut
.keys_required
.as_ref()
.cloned()
.map(|keys_required| keys_required.into_iter().collect::<HashSet<_>>());
Self { inner: shortcut, required_armed_keys }
}
}
impl Deref for Shortcut {
type Target = ui_shortcut::Shortcut;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
/// Produces a clone of the supplied `focus_chain`. This is a separate free function since
/// the `ViewRef`s that `focus_chain` consists of are not trivially cloneable, and it is
/// unclear whether this function is of general interest or not.
///
/// # Performance
///
/// It takes about 1us to clone a ViewRef.
fn clone_focus_chain(
focus_chain: &Vec<fidl_fuchsia_ui_views::ViewRef>,
) -> Vec<fidl_fuchsia_ui_views::ViewRef> {
focus_chain
.iter()
.map(|v| scenic::duplicate_view_ref(v).expect("failed to clone a ViewRef"))
.collect()
}
/// Turns `focus_chain` into a sequence of corresponding `zx::Koid`s. Mainly useful
/// for debugging.
fn koids_of(focus_chain: &Vec<fidl_fuchsia_ui_views::ViewRef>) -> Vec<zx::Koid> {
focus_chain
.iter()
.map(|v| v.reference.as_handle_ref().get_koid().expect("failed to get koid"))
.collect()
}
#[cfg(test)]
mod tests {
use {super::*, fuchsia_async as fasync};
#[fasync::run_singlethreaded(test)]
async fn new_registry_adds_registry() {
let store = RegistryStore::new();
let client_registry = store.add_new_registry().await;
let all_registries = store.get_registries().await;
assert_eq!(1, all_registries.len());
let first_registry = all_registries.first().unwrap().upgrade().unwrap();
assert!(Arc::ptr_eq(&client_registry, &first_registry));
}
#[fasync::run_singlethreaded(test)]
async fn shortcut_populates_keys_hash() {
let fidl_shortcut = ui_shortcut::Shortcut {
keys_required: Some(vec![input::Key::A, input::Key::B]),
..ui_shortcut::Shortcut::EMPTY
};
let shortcut = Shortcut::new(fidl_shortcut);
assert_eq!(
shortcut.required_armed_keys,
Some([input::Key::A, input::Key::B].iter().cloned().collect())
);
}
}