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