• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#stopObservingDevicePresence(ObservingDevicePresenceRequest)})
66  * the system will <a href="https://developer.android.com/guide/components/bound-services">
67  * bind the service</a> when one of the {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
68  * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
69  * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified.
70  *
71  * <p>
72  * The system binding {@link CompanionDeviceService} elevates the priority of the process that
73  * the service is running in, and thus may prevent
74  * <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer">
75  * the Low-memory killer</a> from killing the process at expense of other processes with lower
76  * priority.
77  *
78  * <p>
79  * It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
80  * In such case, the system will bind all declared services, but will deliver
81  * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
82  * only to one "primary" services.
83  * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
84  * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
85  * property.
86  * <pre>{@code
87  * <property
88  *       android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
89  *       android:value="true" />
90  * }</pre>
91  *
92  * <p>
93  * If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate
94  * the "primary" one, the system will pick one of the declared services to use as "primary".
95  *
96  * <p>
97  * If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system
98  * will pick single one of them to use as "primary".
99  */
100 public abstract class CompanionDeviceService extends Service {
101 
102     private static final String LOG_TAG = "CDM_CompanionDeviceService";
103 
104     /**
105      * An intent action for a service to be bound whenever this app's companion device(s)
106      * are nearby or self-managed device(s) report app appeared.
107      *
108      * <p>The app will be kept bound by the system when one of the
109      * {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
110      * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
111      * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified.
112      *
113      * If the app is not running when one of the
114      * {@link DevicePresenceEvent#EVENT_BLE_APPEARED},
115      * {@link DevicePresenceEvent#EVENT_BT_CONNECTED},
116      * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_APPEARED} event is notified, the app will be
117      * kept bound by the system.</p>
118      *
119      * <p>Shortly, the service will be unbound if both
120      * {@link DevicePresenceEvent#EVENT_BLE_DISAPPEARED} and
121      * {@link DevicePresenceEvent#EVENT_BT_DISCONNECTED} are notified, or
122      * {@link DevicePresenceEvent#EVENT_SELF_MANAGED_DISAPPEARED} event is notified.
123      * The app will be eligible for cleanup, unless any other user-visible components are
124      * running.</p>
125      *
126      * If running in background is not essential for the devices that this app can manage,
127      * app should avoid declaring this service.</p>
128      *
129      * <p>The service must also require permission
130      * {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}</p>
131      */
132     public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
133 
134 
135     private final Stub mRemote = new Stub();
136 
137     /**
138      * Called by system whenever a device associated with this app is available.
139      *
140      * @param address the MAC address of the device
141      * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
142      */
143     @Deprecated
144     @MainThread
onDeviceAppeared(@onNull String address)145     public void onDeviceAppeared(@NonNull String address) {
146         // Do nothing. Companion apps can override this function.
147     }
148 
149     /**
150      * Called by system whenever a device associated with this app stops being available.
151      *
152      * Usually this means the device goes out of range or is turned off.
153      *
154      * @param address the MAC address of the device
155      * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
156      */
157     @Deprecated
158     @MainThread
onDeviceDisappeared(@onNull String address)159     public void onDeviceDisappeared(@NonNull String address) {
160         // Do nothing. Companion apps can override this function.
161     }
162 
163     /**
164      * Called by system whenever the system dispatches a message to the app to send it to
165      * an associated device.
166      *
167      * @param messageId system assigned id of the message to be sent
168      * @param associationId association id of the associated device
169      * @param message message to be sent
170      * @hide
171      */
172     @Deprecated
onMessageDispatchedFromSystem(int messageId, int associationId, @NonNull byte[] message)173     public void onMessageDispatchedFromSystem(int messageId, int associationId,
174             @NonNull byte[] message) {
175         Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
176         // do nothing. Companion apps can override this function for system to send messages.
177     }
178 
179     /**
180      * App calls this method when there's a message received from an associated device,
181      * which needs to be dispatched to system for processing.
182      *
183      * <p>Calling app must declare uses-permission
184      * {@link android.Manifest.permission#DELIVER_COMPANION_MESSAGES}</p>
185      *
186      * <p>You need to start the service before calling this method, otherwise the system can't
187      * get the context and the dispatch would fail.</p>
188      *
189      * <p>Note 1: messageId was assigned by the system, and sender should send the messageId along
190      * with the message to the receiver. messageId will later be used for verification purpose.
191      * Misusing the messageId will result in no action.</p>
192      *
193      * <p>Note 2: associationId should be local to your device which is calling this API. It's not
194      * the associationId on your remote device. If you don't have one, you can call
195      * {@link CompanionDeviceManager#associate(AssociationRequest, Executor,
196      * CompanionDeviceManager.Callback)} to create one. Misusing the associationId will result in
197      * {@link DeviceNotAssociatedException}.</p>
198      *
199      * @param messageId id of the message
200      * @param associationId id of the associated device
201      * @param message message received from the associated device
202      * @hide
203      */
204     @Deprecated
205     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
dispatchMessageToSystem(int messageId, int associationId, @NonNull byte[] message)206     public final void dispatchMessageToSystem(int messageId, int associationId,
207             @NonNull byte[] message)
208             throws DeviceNotAssociatedException {
209         Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
210     }
211 
212     /**
213      * Attach the given bidirectional communication streams to be used for
214      * transporting system data between associated devices.
215      * <p>
216      * The companion service providing these streams is responsible for ensuring
217      * that all data is transported accurately and in-order between the two
218      * devices, including any fragmentation and re-assembly when carried over a
219      * size-limited transport.
220      * <p>
221      * As an example, it's valid to provide streams obtained from a
222      * {@link BluetoothSocket} to this method, since {@link BluetoothSocket}
223      * meets the API contract described above.
224      * <p>
225      * This method passes through to
226      * {@link CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}
227      * for your convenience if you get callbacks in this class.
228      *
229      * @param associationId id of the associated device
230      * @param in already connected stream of data incoming from remote
231      *            associated device
232      * @param out already connected stream of data outgoing to remote associated
233      *            device
234      */
235     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
attachSystemDataTransport(int associationId, @NonNull InputStream in, @NonNull OutputStream out)236     public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
237             @NonNull OutputStream out) throws DeviceNotAssociatedException {
238         getSystemService(CompanionDeviceManager.class)
239                 .attachSystemDataTransport(associationId,
240                         Objects.requireNonNull(in),
241                         Objects.requireNonNull(out));
242     }
243 
244     /**
245      * Detach any bidirectional communication streams previously configured
246      * through {@link #attachSystemDataTransport}.
247      * <p>
248      * This method passes through to
249      * {@link CompanionDeviceManager#detachSystemDataTransport(int)}
250      * for your convenience if you get callbacks in this class.
251      *
252      * @param associationId id of the associated device
253      */
254     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
detachSystemDataTransport(int associationId)255     public final void detachSystemDataTransport(int associationId)
256             throws DeviceNotAssociatedException {
257         getSystemService(CompanionDeviceManager.class)
258                 .detachSystemDataTransport(associationId);
259     }
260 
261     /**
262      * Called by the system when an associated device is nearby or connected.
263      *
264      * @param associationInfo A record for the companion device.
265      * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
266      */
267     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
268     @Deprecated
269     @MainThread
onDeviceAppeared(@onNull AssociationInfo associationInfo)270     public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
271         if (!associationInfo.isSelfManaged()) {
272             onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
273         }
274     }
275 
276     /**
277      * Called by the system when an associated device is out of range or disconnected.
278      *
279      * @param associationInfo A record for the companion device.
280      * @deprecated use {@link #onDevicePresenceEvent(DevicePresenceEvent)}} instead.
281      */
282     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
283     @Deprecated
284     @MainThread
onDeviceDisappeared(@onNull AssociationInfo associationInfo)285     public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
286         if (!associationInfo.isSelfManaged()) {
287             onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
288         }
289     }
290 
291     /**
292      * Called by the system when an associated device's presence state changes.
293      *
294      * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
295      */
296     @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
297     @MainThread
onDevicePresenceEvent(@onNull DevicePresenceEvent event)298     public void onDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
299         // Do nothing. Companion apps can override this function.
300     }
301 
302     @Nullable
303     @Override
onBind(@onNull Intent intent)304     public final IBinder onBind(@NonNull Intent intent) {
305         if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
306             onBindCompanionDeviceService(intent);
307             return mRemote;
308         }
309         Log.w(LOG_TAG,
310                 "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + "): " + intent);
311         return null;
312     }
313 
314     /**
315      * Used to track the state of Binder connection in CTS tests.
316      * @hide
317      */
318     @TestApi
onBindCompanionDeviceService(@onNull Intent intent)319     public void onBindCompanionDeviceService(@NonNull Intent intent) {
320     }
321 
322     private class Stub extends ICompanionDeviceService.Stub {
323         final Handler mMainHandler = Handler.getMain();
324         final CompanionDeviceService mService = CompanionDeviceService.this;
325 
326         @Override
onDeviceAppeared(AssociationInfo associationInfo)327         public void onDeviceAppeared(AssociationInfo associationInfo) {
328             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceAppeared(associationInfo));
329         }
330 
331         @Override
onDeviceDisappeared(AssociationInfo associationInfo)332         public void onDeviceDisappeared(AssociationInfo associationInfo) {
333             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
334         }
335 
336         @Override
onDevicePresenceEvent(DevicePresenceEvent event)337         public void onDevicePresenceEvent(DevicePresenceEvent event) {
338             if (Flags.devicePresence()) {
339                 mMainHandler.postAtFrontOfQueue(() -> mService.onDevicePresenceEvent(event));
340             }
341         }
342     }
343 }
344