• 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.MainThread;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.TestApi;
25 import android.app.Service;
26 import android.content.Intent;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.util.Log;
30 
31 import java.util.Objects;
32 
33 /**
34  * A service that receives calls from the system when the associated companion device appears
35  * nearby or is connected, as well as when the device is no longer "present" or connected.
36  * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}.
37  *
38  * <p>
39  * Companion applications must create a service that {@code extends}
40  * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the
41  * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission
42  * (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}),
43  * as well as add an intent filter for the "android.companion.CompanionDeviceService" action
44  * (see {@link #SERVICE_INTERFACE}).
45  *
46  * <p>
47  * Following is an example of such declaration:
48  * <pre>{@code
49  * <service
50  *        android:name=".CompanionService"
51  *        android:label="@string/service_name"
52  *        android:exported="true"
53  *        android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
54  *    <intent-filter>
55  *        <action android:name="android.companion.CompanionDeviceService" />
56  *    </intent-filter>
57  * </service>
58  * }</pre>
59  *
60  * <p>
61  * If the companion application has requested observing device presence (see
62  * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will
63  * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a>
64  * when it detects the device nearby (for BLE devices) or when the device is connected
65  * (for Bluetooth devices).
66  *
67  * <p>
68  * The system binding {@link CompanionDeviceService} elevates the priority of the process that
69  * the service is running in, and thus may prevent
70  * <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer">
71  * the Low-memory killer</a> from killing the process at expense of other processes with lower
72  * priority.
73  *
74  * <p>
75  * It is possible for an application to declare multiple {@link CompanionDeviceService}-s.
76  * In such case, the system will bind all declared services, but will deliver
77  * {@link #onDeviceAppeared(AssociationInfo)} and {@link #onDeviceDisappeared(AssociationInfo)}
78  * only to one "primary" services.
79  * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary"
80  * service using "android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE" service level
81  * property.
82  * <pre>{@code
83  * <property
84  *       android:name="android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE"
85  *       android:value="true" />
86  * }</pre>
87  *
88  * <p>
89  * If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate
90  * the "primary" one, the system will pick one of the declared services to use as "primary".
91  *
92  * <p>
93  * If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system
94  * will pick single one of them to use as "primary".
95  */
96 public abstract class CompanionDeviceService extends Service {
97 
98     private static final String LOG_TAG = "CompanionDeviceService";
99 
100     /**
101      * An intent action for a service to be bound whenever this app's companion device(s)
102      * are nearby.
103      *
104      * <p>The app will be kept alive for as long as the device is nearby or companion app reports
105      * appeared.
106      * If the app is not running at the time device gets connected, the app will be woken up.</p>
107      *
108      * <p>Shortly after the device goes out of range or the companion app reports disappeared,
109      * the service will be unbound, and the app will be eligible for cleanup, unless any other
110      * user-visible components are running.</p>
111      *
112      * If running in background is not essential for the devices that this app can manage,
113      * app should avoid declaring this service.</p>
114      *
115      * <p>The service must also require permission
116      * {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}</p>
117      */
118     public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
119 
120     private final Stub mRemote = new Stub();
121 
122     /**
123      * Called by system whenever a device associated with this app is available.
124      *
125      * @param address the MAC address of the device
126      * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
127      */
128     @Deprecated
129     @MainThread
onDeviceAppeared(@onNull String address)130     public void onDeviceAppeared(@NonNull String address) {
131         // Do nothing. Companion apps can override this function.
132     }
133 
134     /**
135      * Called by system whenever a device associated with this app stops being available.
136      *
137      * Usually this means the device goes out of range or is turned off.
138      *
139      * @param address the MAC address of the device
140      * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
141      */
142     @Deprecated
143     @MainThread
onDeviceDisappeared(@onNull String address)144     public void onDeviceDisappeared(@NonNull String address) {
145         // Do nothing. Companion apps can override this function.
146     }
147 
148     /**
149      * Called by system whenever the system dispatches a message to the app to send it to
150      * an associated device.
151      *
152      * @param messageId system assigned id of the message to be sent
153      * @param associationId association id of the associated device
154      * @param message message to be sent
155      *
156      * @hide
157      */
158     @MainThread
onDispatchMessage(int messageId, int associationId, @NonNull byte[] message)159     public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
160         // do nothing. Companion apps can override this function for system to send messages.
161     }
162 
163     /**
164      * App calls this method when there's a message received from an associated device,
165      * which needs to be dispatched to system for processing.
166      *
167      * <p>Calling app must declare uses-permission
168      * {@link android.Manifest.permission#DELIVER_COMPANION_MESSAGES}</p>
169      *
170      * @param messageId id of the message
171      * @param associationId id of the associated device
172      * @param message messaged received from the associated device
173      *
174      * @hide
175      */
176     @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
dispatchMessage(int messageId, int associationId, @NonNull byte[] message)177     public final void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
178         CompanionDeviceManager companionDeviceManager =
179                 getSystemService(CompanionDeviceManager.class);
180         companionDeviceManager.dispatchMessage(messageId, associationId, message);
181     }
182 
183     /**
184      * Called by system whenever a device associated with this app is connected.
185      *
186      * @param associationInfo A record for the companion device.
187      */
188     @MainThread
onDeviceAppeared(@onNull AssociationInfo associationInfo)189     public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
190         if (!associationInfo.isSelfManaged()) {
191             onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
192         }
193     }
194 
195     /**
196      * Called by system whenever a device associated with this app is disconnected.
197      *
198      * @param associationInfo A record for the companion device.
199      */
200     @MainThread
onDeviceDisappeared(@onNull AssociationInfo associationInfo)201     public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
202         if (!associationInfo.isSelfManaged()) {
203             onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
204         }
205     }
206 
207     @Nullable
208     @Override
onBind(@onNull Intent intent)209     public final IBinder onBind(@NonNull Intent intent) {
210         if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
211             onBindCompanionDeviceService(intent);
212             return mRemote;
213         }
214         Log.w(LOG_TAG,
215                 "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + "): " + intent);
216         return null;
217     }
218 
219     /**
220      * Used to track the state of Binder connection in CTS tests.
221      * @hide
222      */
223     @TestApi
onBindCompanionDeviceService(@onNull Intent intent)224     public void onBindCompanionDeviceService(@NonNull Intent intent) {
225     }
226 
227     private class Stub extends ICompanionDeviceService.Stub {
228         final Handler mMainHandler = Handler.getMain();
229         final CompanionDeviceService mService = CompanionDeviceService.this;
230 
231         @Override
onDeviceAppeared(AssociationInfo associationInfo)232         public void onDeviceAppeared(AssociationInfo associationInfo) {
233             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceAppeared(associationInfo));
234         }
235 
236         @Override
onDeviceDisappeared(AssociationInfo associationInfo)237         public void onDeviceDisappeared(AssociationInfo associationInfo) {
238             mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
239         }
240 
241         @Override
onDispatchMessage(int messageId, int associationId, @NonNull byte[] message)242         public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
243             mMainHandler.postAtFrontOfQueue(
244                     () -> mService.onDispatchMessage(messageId, associationId, message));
245         }
246     }
247 }
248