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