1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package android.companion;
19 
20 import android.annotation.FlaggedApi;
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.TestApi;
26 import android.app.Service;
27 import android.bluetooth.BluetoothSocket;
28 import android.content.Intent;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.util.Log;
32 
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.util.Objects;
36 import java.util.concurrent.Executor;
37 
38 /**
39  * A service that receives calls from the system with device events.
40  *
41  * <p>
42  * Companion applications must create a service that {@code extends}
43  * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the
44  * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission
45  * (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}),
46  * as well as add an intent filter for the "android.companion.CompanionDeviceService" action
47  * (see {@link #SERVICE_INTERFACE}).
48  *
49  * <p>
50  * Following is an example of such declaration:
51  * <pre>{@code
52  * <service
53  *        android:name=".CompanionService"
54  *        android:label="@string/service_name"
55  *        android:exported="true"
56  *        android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
57  *    <intent-filter>
58  *        <action android:name="android.companion.CompanionDeviceService" />
59  *    </intent-filter>
60  * </service>
61  * }</pre>
62  *
63  * <p>
64  * If the companion application has requested observing device presence (see
65  * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will
66  * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a>
67  * when it detects the device nearby (for BLE devices) or when the device is connected
68  * (for Bluetooth devices).
69  *
70  * <p>
71  * The system binding {@link CompanionDeviceService} elevates the priority of the process that
72  * the service is running in, and thus may prevent
73  * <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer">
74  * the Low-memory killer</a> from killing the process at expense of other processes with lower
75  * priority.
76  *
77  * <p>
78  * It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
79  * In such case, the system will bind all declared services, but will deliver
80  * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
81  * only to one "primary" services.
82  * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
83  * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
84  * property.
85  * <pre>{@code
86  * <property
87  *       android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
88  *       android:value="true" />
89  * }</pre>
90  *
91  * <p>
92  * If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate
93  * the "primary" one, the system will pick one of the declared services to use as "primary".
94  *
95  * <p>
96  * If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system
97  * will pick single one of them to use as "primary".
98  */
99 public abstract class CompanionDeviceService extends Service {
100 
101     private static final String LOG_TAG = "CDM_CompanionDeviceService";
102 
103     /**
104      * An intent action for a service to be bound whenever this app's companion device(s)
105      * are nearby.
106      *
107      * <p>The app will be kept alive for as long as the device is nearby or companion app reports
108      * appeared.
109      * If the app is not running at the time device gets connected, the app will be woken up.</p>
110      *
111      * <p>Shortly after the device goes out of range or the companion app reports disappeared,
112      * the service will be unbound, and the app will be eligible for cleanup, unless any other
113      * user-visible components are running.</p>
114      *
115      * If running in background is not essential for the devices that this app can manage,
116      * app should avoid declaring this service.</p>
117      *
118      * <p>The service must also require permission
119      * {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}</p>
120      */
121     public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
122 
123 
124     private final Stub mRemote = new Stub();
125 
126     /**
127      * Called by system whenever a device associated with this app is available.
128      *
129      * @param address the MAC address of the device
130      * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
131      */
132     @Deprecated
133     @MainThread
onDeviceAppeared(@onNull String address)134     public void onDeviceAppeared(@NonNull String address) {
135         // Do nothing. Companion apps can override this function.
136     }
137 
138     /**
139      * Called by system whenever a device associated with this app stops being available.
140      *
141      * Usually this means the device goes out of range or is turned off.
142      *
143      * @param address the MAC address of the device
144      * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
145      */
146     @Deprecated
147     @MainThread
onDeviceDisappeared(@onNull String address)148     public void onDeviceDisappeared(@NonNull String address) {
149         // Do nothing. Companion apps can override this function.
150     }
151 
152     /**
153      * Called by system whenever the system dispatches a message to the app to send it to
154      * an associated device.
155      *
156      * @param messageId system assigned id of the message to be sent
157      * @param associationId association id of the associated device
158      * @param message message to be sent
159      * @hide
160      */
161     @Deprecated
onMessageDispatchedFromSystem(int messageId, int associationId, @NonNull byte[] message)162     public void onMessageDispatchedFromSystem(int messageId, int associationId,
163             @NonNull byte[] message) {
164         Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
165         // do nothing. Companion apps can override this function for system to send messages.
166     }
167 
168     /**
169      * App calls this method when there's a message received from an associated device,
170      * which needs to be dispatched to system for processing.
171      *
172      * <p>Calling app must declare uses-permission
173      * {@link android.Manifest.permission#DELIVER_COMPANION_MESSAGES}</p>
174      *
175      * <p>You need to start the service before calling this method, otherwise the system can't
176      * get the context and the dispatch would fail.</p>
177      *
178      * <p>Note 1: messageId was assigned by the system, and sender should send the messageId along
179      * with the message to the receiver. messageId will later be used for verification purpose.
180      * Misusing the messageId will result in no action.</p>
181      *
182      * <p>Note 2: associationId should be local to your device which is calling this API. It's not
183      * the associationId on your remote device. If you don't have one, you can call
184      * {@link CompanionDeviceManager#associate(AssociationRequest, Executor,
185      * CompanionDeviceManager.Callback)} to create one. Misusing the associationId will result in
186      * {@link DeviceNotAssociatedException}.</p>
187      *
188      * @param messageId id of the message
189      * @param associationId id of the associated device
190      * @param message message received from the associated device
191      * @hide
192      */
193     @Deprecated
194     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
dispatchMessageToSystem(int messageId, int associationId, @NonNull byte[] message)195     public final void dispatchMessageToSystem(int messageId, int associationId,
196             @NonNull byte[] message)
197             throws DeviceNotAssociatedException {
198         Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
199     }
200 
201     /**
202      * Attach the given bidirectional communication streams to be used for
203      * transporting system data between associated devices.
204      * <p>
205      * The companion service providing these streams is responsible for ensuring
206      * that all data is transported accurately and in-order between the two
207      * devices, including any fragmentation and re-assembly when carried over a
208      * size-limited transport.
209      * <p>
210      * As an example, it's valid to provide streams obtained from a
211      * {@link BluetoothSocket} to this method, since {@link BluetoothSocket}
212      * meets the API contract described above.
213      * <p>
214      * This method passes through to
215      * {@link CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}
216      * for your convenience if you get callbacks in this class.
217      *
218      * @param associationId id of the associated device
219      * @param in already connected stream of data incoming from remote
220      *            associated device
221      * @param out already connected stream of data outgoing to remote associated
222      *            device
223      */
224     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
attachSystemDataTransport(int associationId, @NonNull InputStream in, @NonNull OutputStream out)225     public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
226             @NonNull OutputStream out) throws DeviceNotAssociatedException {
227         getSystemService(CompanionDeviceManager.class)
228                 .attachSystemDataTransport(associationId,
229                         Objects.requireNonNull(in),
230                         Objects.requireNonNull(out));
231     }
232 
233     /**
234      * Detach any bidirectional communication streams previously configured
235      * through {@link #attachSystemDataTransport}.
236      * <p>
237      * This method passes through to
238      * {@link CompanionDeviceManager#detachSystemDataTransport(int)}
239      * for your convenience if you get callbacks in this class.
240      *
241      * @param associationId id of the associated device
242      */
243     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
detachSystemDataTransport(int associationId)244     public final void detachSystemDataTransport(int associationId)
245             throws DeviceNotAssociatedException {
246         getSystemService(CompanionDeviceManager.class)
247                 .detachSystemDataTransport(associationId);
248     }
249 
250     // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
251     /**
252      * Called by system whenever a device associated with this app is connected.
253      *
254      * @param associationInfo A record for the companion device.
255      */
256     @MainThread
onDeviceAppeared(@onNull AssociationInfo associationInfo)257     public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
258         if (!associationInfo.isSelfManaged()) {
259             onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
260         }
261     }
262 
263     // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
264     /**
265      * Called by system whenever a device associated with this app is disconnected.
266      *
267      * @param associationInfo A record for the companion device.
268      */
269     @MainThread
onDeviceDisappeared(@onNull AssociationInfo associationInfo)270     public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
271         if (!associationInfo.isSelfManaged()) {
272             onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
273         }
274     }
275 
276     /**
277      * Called by the system during device events.
278      *
279      * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
280      */
281     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
282     @MainThread
onDevicePresenceEvent(@onNull DevicePresenceEvent event)283     public void onDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
284         // Do nothing. Companion apps can override this function.
285     }
286 
287     @Nullable
288     @Override
onBind(@onNull Intent intent)289     public final IBinder onBind(@NonNull Intent intent) {
290         if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
291             onBindCompanionDeviceService(intent);
292             return mRemote;
293         }
294         Log.w(LOG_TAG,
295                 "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + "): " + intent);
296         return null;
297     }
298 
299     /**
300      * Used to track the state of Binder connection in CTS tests.
301      * @hide
302      */
303     @TestApi
onBindCompanionDeviceService(@onNull Intent intent)304     public void onBindCompanionDeviceService(@NonNull Intent intent) {
305     }
306 
307     private class Stub extends ICompanionDeviceService.Stub {
308         final Handler mMainHandler = Handler.getMain();
309         final CompanionDeviceService mService = CompanionDeviceService.this;
310 
311         @Override
onDeviceAppeared(AssociationInfo associationInfo)312         public void onDeviceAppeared(AssociationInfo associationInfo) {
313             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceAppeared(associationInfo));
314         }
315 
316         @Override
onDeviceDisappeared(AssociationInfo associationInfo)317         public void onDeviceDisappeared(AssociationInfo associationInfo) {
318             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
319         }
320 
321         @Override
onDevicePresenceEvent(DevicePresenceEvent event)322         public void onDevicePresenceEvent(DevicePresenceEvent event) {
323             if (Flags.devicePresence()) {
324                 mMainHandler.postAtFrontOfQueue(() -> mService.onDevicePresenceEvent(event));
325             }
326         }
327     }
328 }
329