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