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