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.bluetooth.BluetoothSocket; 27 import android.content.Intent; 28 import android.os.Handler; 29 import android.os.IBinder; 30 import android.util.Log; 31 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.util.Objects; 35 import java.util.concurrent.Executor; 36 37 /** 38 * A service that receives calls from the system when the associated companion device appears 39 * nearby or is connected, as well as when the device is no longer "present" or connected. 40 * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}. 41 * 42 * <p> 43 * Companion applications must create a service that {@code extends} 44 * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the 45 * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission 46 * (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}), 47 * as well as add an intent filter for the "android.companion.CompanionDeviceService" action 48 * (see {@link #SERVICE_INTERFACE}). 49 * 50 * <p> 51 * Following is an example of such declaration: 52 * <pre>{@code 53 * <service 54 * android:name=".CompanionService" 55 * android:label="@string/service_name" 56 * android:exported="true" 57 * android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE"> 58 * <intent-filter> 59 * <action android:name="android.companion.CompanionDeviceService" /> 60 * </intent-filter> 61 * </service> 62 * }</pre> 63 * 64 * <p> 65 * If the companion application has requested observing device presence (see 66 * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will 67 * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a> 68 * when it detects the device nearby (for BLE devices) or when the device is connected 69 * (for Bluetooth devices). 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. 107 * 108 * <p>The app will be kept alive for as long as the device is nearby or companion app reports 109 * appeared. 110 * If the app is not running at the time device gets connected, the app will be woken up.</p> 111 * 112 * <p>Shortly after the device goes out of range or the companion app reports disappeared, 113 * the service will be unbound, and the app will be eligible for cleanup, unless any other 114 * user-visible components are running.</p> 115 * 116 * If running in background is not essential for the devices that this app can manage, 117 * app should avoid declaring this service.</p> 118 * 119 * <p>The service must also require permission 120 * {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}</p> 121 */ 122 public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; 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 /** 251 * Called by system whenever a device associated with this app is connected. 252 * 253 * @param associationInfo A record for the companion device. 254 */ 255 @MainThread onDeviceAppeared(@onNull AssociationInfo associationInfo)256 public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) { 257 if (!associationInfo.isSelfManaged()) { 258 onDeviceAppeared(associationInfo.getDeviceMacAddressAsString()); 259 } 260 } 261 262 /** 263 * Called by system whenever a device associated with this app is disconnected. 264 * 265 * @param associationInfo A record for the companion device. 266 */ 267 @MainThread onDeviceDisappeared(@onNull AssociationInfo associationInfo)268 public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) { 269 if (!associationInfo.isSelfManaged()) { 270 onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString()); 271 } 272 } 273 274 @Nullable 275 @Override onBind(@onNull Intent intent)276 public final IBinder onBind(@NonNull Intent intent) { 277 if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) { 278 onBindCompanionDeviceService(intent); 279 return mRemote; 280 } 281 Log.w(LOG_TAG, 282 "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + "): " + intent); 283 return null; 284 } 285 286 /** 287 * Used to track the state of Binder connection in CTS tests. 288 * @hide 289 */ 290 @TestApi onBindCompanionDeviceService(@onNull Intent intent)291 public void onBindCompanionDeviceService(@NonNull Intent intent) { 292 } 293 294 private class Stub extends ICompanionDeviceService.Stub { 295 final Handler mMainHandler = Handler.getMain(); 296 final CompanionDeviceService mService = CompanionDeviceService.this; 297 298 @Override onDeviceAppeared(AssociationInfo associationInfo)299 public void onDeviceAppeared(AssociationInfo associationInfo) { 300 mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceAppeared(associationInfo)); 301 } 302 303 @Override onDeviceDisappeared(AssociationInfo associationInfo)304 public void onDeviceDisappeared(AssociationInfo associationInfo) { 305 mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo)); 306 } 307 } 308 } 309