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;