Skip to main content

cranpose_services/
wearable.rs

1//! Messages and byte streams between an application and the same application
2//! on the paired phone or watch: a watch app streams its microphone to the
3//! phone, and the phone sends its results back.
4//!
5//! On Android the link is the Wear OS Data Layer. An application turns it on
6//! with `cranpose { services.add("wearable") }`, which brings the framework's
7//! Java and Google Play services' wearable library into its build. On other
8//! platforms, and in a build without the service, every call returns
9//! [`WearableError::Unavailable`].
10//!
11//! **Sending.** [`wearable_peers`] lists the connected devices,
12//! [`send_wearable_message`] delivers a small message, and
13//! [`open_wearable_stream`] opens a byte stream for continuous data such as
14//! audio. Each call blocks until the platform answers, so call them off the
15//! UI thread.
16//!
17//! **Receiving.** The application installs one [`WearableReceiver`] with
18//! [`set_wearable_receiver`]. Events arrive on a platform thread. A
19//! [`WearableStream`] is read until it ends, on whatever thread the receiver
20//! hands it to. An event that arrives while no receiver is installed is
21//! dropped.
22
23use std::{
24    io::{Read, Write},
25    sync::Arc,
26};
27
28use crate::registry::ServiceRegistry;
29
30/// A connected phone or watch.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct WearablePeer {
33    /// The platform's identifier of the device, the `peer` the other calls take.
34    pub id: String,
35    /// The name the person gave the device.
36    pub name: String,
37}
38
39/// Why a call to the paired device did not happen.
40#[derive(thiserror::Error, Debug)]
41pub enum WearableError {
42    /// This platform or this build has no link to a paired device.
43    #[error("there is no link to a paired device in this build")]
44    Unavailable,
45    /// The platform reported a failure, for example a device out of range.
46    #[error("the paired device link failed: {0}")]
47    Failed(String),
48}
49
50/// A byte stream the paired device opened to this application.
51pub struct WearableStream {
52    /// The device that opened it.
53    pub peer: String,
54    /// The path it was opened under.
55    pub path: String,
56    reader: Box<dyn Read + Send>,
57}
58
59impl WearableStream {
60    /// A stream from `peer` under `path`, read through `reader`. Platform
61    /// backends create it.
62    pub fn new(peer: String, path: String, reader: Box<dyn Read + Send>) -> Self {
63        Self { peer, path, reader }
64    }
65}
66
67impl Read for WearableStream {
68    fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
69        self.reader.read(buffer)
70    }
71}
72
73/// Something the paired device sent.
74pub enum WearableEvent {
75    /// A message sent with [`send_wearable_message`] on the other device.
76    Message {
77        /// The device that sent it.
78        peer: String,
79        /// The path it was sent under.
80        path: String,
81        /// Its bytes.
82        data: Vec<u8>,
83    },
84    /// A stream opened with [`open_wearable_stream`] on the other device.
85    Stream(WearableStream),
86}
87
88/// Receives what the paired device sends.
89pub trait WearableReceiver: Send + Sync {
90    /// Handles one event, on a platform thread.
91    fn receive(&self, event: WearableEvent);
92}
93
94impl<F: Fn(WearableEvent) + Send + Sync> WearableReceiver for F {
95    fn receive(&self, event: WearableEvent) {
96        self(event);
97    }
98}
99
100/// The platform's link to the paired device, installed by the platform
101/// backend with [`set_platform_wearable_link`].
102pub trait WearableLink: Send + Sync {
103    /// The connected devices.
104    fn peers(&self) -> Result<Vec<WearablePeer>, WearableError>;
105    /// Delivers `data` to `peer` under `path`.
106    fn send_message(&self, peer: &str, path: &str, data: &[u8]) -> Result<(), WearableError>;
107    /// Opens a byte stream to `peer` under `path`.
108    fn open_stream(&self, peer: &str, path: &str) -> Result<Box<dyn Write + Send>, WearableError>;
109}
110
111static PLATFORM: ServiceRegistry<dyn WearableLink> = ServiceRegistry::new();
112static RECEIVER: ServiceRegistry<dyn WearableReceiver> = ServiceRegistry::new();
113
114/// Installs the platform's link, or removes it with `None`; without one,
115/// every call returns [`WearableError::Unavailable`]. Platform backends call it.
116pub fn set_platform_wearable_link(link: Option<Arc<dyn WearableLink>>) {
117    match link {
118        Some(link) => PLATFORM.set(link),
119        None => PLATFORM.clear(),
120    }
121}
122
123fn link() -> Result<Arc<dyn WearableLink>, WearableError> {
124    PLATFORM.get().ok_or(WearableError::Unavailable)
125}
126
127/// The connected phones or watches.
128pub fn wearable_peers() -> Result<Vec<WearablePeer>, WearableError> {
129    link()?.peers()
130}
131
132/// Delivers `data` to the application on `peer` under `path`.
133pub fn send_wearable_message(peer: &str, path: &str, data: &[u8]) -> Result<(), WearableError> {
134    link()?.send_message(peer, path, data)
135}
136
137/// Opens a byte stream to the application on `peer` under `path`; the other
138/// device receives it as [`WearableEvent::Stream`]. Dropping the writer
139/// closes the stream.
140pub fn open_wearable_stream(
141    peer: &str,
142    path: &str,
143) -> Result<Box<dyn Write + Send>, WearableError> {
144    link()?.open_stream(peer, path)
145}
146
147/// Installs the application's receiver of what the paired device sends,
148/// replacing any earlier one, or removes it with `None`; later events are then
149/// dropped.
150pub fn set_wearable_receiver(receiver: Option<Arc<dyn WearableReceiver>>) {
151    match receiver {
152        Some(receiver) => RECEIVER.set(receiver),
153        None => RECEIVER.clear(),
154    }
155}
156
157/// Hands an event from the platform to the application's receiver, or drops
158/// it when there is none. Platform backends call it.
159pub fn publish_wearable_event(event: WearableEvent) {
160    match RECEIVER.get() {
161        Some(receiver) => receiver.receive(event),
162        None => log::warn!(
163            "a paired device sent something, and the application has no wearable receiver"
164        ),
165    }
166}
167
168#[cfg(test)]
169#[path = "tests/wearable_tests.rs"]
170mod tests;