1 /* 2 * Copyright (C) 2017 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 package android.companion; 18 19 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING; 20 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION; 21 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER; 22 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH; 23 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SuppressLint; 29 import android.annotation.SystemApi; 30 import android.annotation.SystemService; 31 import android.annotation.UserHandleAware; 32 import android.app.Activity; 33 import android.app.ActivityManager; 34 import android.app.ActivityManagerInternal; 35 import android.app.NotificationManager; 36 import android.app.PendingIntent; 37 import android.bluetooth.BluetoothAdapter; 38 import android.bluetooth.BluetoothDevice; 39 import android.companion.datatransfer.PermissionSyncRequest; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentSender; 44 import android.content.pm.PackageManager; 45 import android.net.MacAddress; 46 import android.os.Binder; 47 import android.os.Handler; 48 import android.os.OutcomeReceiver; 49 import android.os.ParcelFileDescriptor; 50 import android.os.RemoteException; 51 import android.os.UserHandle; 52 import android.service.notification.NotificationListenerService; 53 import android.util.ExceptionUtils; 54 import android.util.Log; 55 import android.util.SparseArray; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.util.CollectionUtils; 59 import com.android.server.LocalServices; 60 61 import libcore.io.IoUtils; 62 63 import java.io.IOException; 64 import java.io.InputStream; 65 import java.io.OutputStream; 66 import java.lang.annotation.Retention; 67 import java.lang.annotation.RetentionPolicy; 68 import java.util.ArrayList; 69 import java.util.Collections; 70 import java.util.Iterator; 71 import java.util.List; 72 import java.util.Objects; 73 import java.util.concurrent.Executor; 74 import java.util.function.Consumer; 75 76 /** 77 * System level service for managing companion devices 78 * 79 * See <a href="{@docRoot}guide/topics/connectivity/companion-device-pairing">this guide</a> 80 * for a usage example. 81 * 82 * <p>To obtain an instance call {@link Context#getSystemService}({@link 83 * Context#COMPANION_DEVICE_SERVICE}) Then, call {@link #associate(AssociationRequest, 84 * Callback, Handler)} to initiate the flow of associating current package with a 85 * device selected by user.</p> 86 * 87 * @see CompanionDeviceManager#associate 88 * @see AssociationRequest 89 */ 90 @SuppressLint("LongLogTag") 91 @SystemService(Context.COMPANION_DEVICE_SERVICE) 92 public final class CompanionDeviceManager { 93 94 private static final boolean DEBUG = false; 95 private static final String LOG_TAG = "CDM_CompanionDeviceManager"; 96 97 /** @hide */ 98 @IntDef(prefix = {"RESULT_"}, value = { 99 RESULT_OK, 100 RESULT_CANCELED, 101 RESULT_USER_REJECTED, 102 RESULT_DISCOVERY_TIMEOUT, 103 RESULT_INTERNAL_ERROR 104 }) 105 @Retention(RetentionPolicy.SOURCE) 106 public @interface ResultCode {} 107 108 /** 109 * The result code to propagate back to the user activity, indicates the association 110 * is created successfully. 111 */ 112 public static final int RESULT_OK = -1; 113 114 /** 115 * The result code to propagate back to the user activity, indicates if the association dialog 116 * is implicitly cancelled. 117 * E.g. phone is locked, switch to another app or press outside the dialog. 118 */ 119 public static final int RESULT_CANCELED = 0; 120 121 /** 122 * The result code to propagate back to the user activity, indicates the association dialog 123 * is explicitly declined by the users. 124 */ 125 public static final int RESULT_USER_REJECTED = 1; 126 127 /** 128 * The result code to propagate back to the user activity, indicates the association 129 * dialog is dismissed if there's no device found after 20 seconds. 130 */ 131 public static final int RESULT_DISCOVERY_TIMEOUT = 2; 132 133 /** 134 * The result code to propagate back to the user activity, indicates the internal error 135 * in CompanionDeviceManager. 136 */ 137 public static final int RESULT_INTERNAL_ERROR = 3; 138 139 /** 140 * Requesting applications will receive the String in {@link Callback#onFailure} if the 141 * association dialog is explicitly declined by the users. E.g. press the Don't allow 142 * button. 143 * 144 * @hide 145 */ 146 public static final String REASON_USER_REJECTED = "user_rejected"; 147 148 /** 149 * Requesting applications will receive the String in {@link Callback#onFailure} if there's 150 * no devices found after 20 seconds. 151 * 152 * @hide 153 */ 154 public static final String REASON_DISCOVERY_TIMEOUT = "discovery_timeout"; 155 156 /** 157 * Requesting applications will receive the String in {@link Callback#onFailure} if there's 158 * an internal error. 159 * 160 * @hide 161 */ 162 public static final String REASON_INTERNAL_ERROR = "internal_error"; 163 164 /** 165 * Requesting applications will receive the String in {@link Callback#onFailure} if the 166 * association dialog is implicitly cancelled. E.g. phone is locked, switch to 167 * another app or press outside the dialog. 168 * 169 * @hide 170 */ 171 public static final String REASON_CANCELED = "canceled"; 172 173 /** @hide */ 174 @IntDef(flag = true, prefix = { "FLAG_" }, value = { 175 FLAG_CALL_METADATA, 176 }) 177 @Retention(RetentionPolicy.SOURCE) 178 public @interface DataSyncTypes {} 179 180 /** 181 * Used by {@link #enableSystemDataSyncForTypes(int, int)}}. 182 * Sync call metadata like muting, ending and silencing a call. 183 * 184 */ 185 public static final int FLAG_CALL_METADATA = 1; 186 187 /** 188 * A device, returned in the activity result of the {@link IntentSender} received in 189 * {@link Callback#onDeviceFound} 190 * 191 * Type is: 192 * <ul> 193 * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> 194 * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> 195 * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> 196 * </ul> 197 * 198 * @deprecated use {@link AssociationInfo#getAssociatedDevice()} instead. 199 */ 200 @Deprecated 201 public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; 202 203 /** 204 * Extra field name for the {@link AssociationInfo} object, included into 205 * {@link android.content.Intent} which application receive in 206 * {@link Activity#onActivityResult(int, int, Intent)} after the application's 207 * {@link AssociationRequest} was successfully processed and an association was created. 208 */ 209 public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"; 210 211 /** 212 * The package name of the companion device discovery component. 213 * 214 * @hide 215 */ 216 public static final String COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME = 217 "com.android.companiondevicemanager"; 218 219 /** 220 * Callback for applications to receive updates about and the outcome of 221 * {@link AssociationRequest} issued via {@code associate()} call. 222 * 223 * <p> 224 * The {@link Callback#onAssociationPending(IntentSender)} is invoked after the 225 * {@link AssociationRequest} has been checked by the Companion Device Manager Service and is 226 * pending user's approval. 227 * 228 * The {@link IntentSender} received as an argument to 229 * {@link Callback#onAssociationPending(IntentSender)} "encapsulates" an {@link Activity} 230 * that has UI for the user to: 231 * <ul> 232 * <li> 233 * choose the device to associate the application with (if multiple eligible devices are 234 * available) 235 * </li> 236 * <li>confirm the association</li> 237 * <li> 238 * approve the privileges the application will be granted if the association is to be created 239 * </li> 240 * </ul> 241 * 242 * If the Companion Device Manager Service needs to scan for the devices, the {@link Activity} 243 * will also display the status and the progress of the scan. 244 * 245 * Note that Companion Device Manager Service will only start the scanning after the 246 * {@link Activity} was launched and became visible. 247 * 248 * Applications are expected to launch the UI using the received {@link IntentSender} via 249 * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}. 250 * </p> 251 * 252 * <p> 253 * Upon receiving user's confirmation Companion Device Manager Service will create an 254 * association and will send an {@link AssociationInfo} object that represents the created 255 * association back to the application both via 256 * {@link Callback#onAssociationCreated(AssociationInfo)} and 257 * via {@link Activity#setResult(int, Intent)}. 258 * In the latter the {@code resultCode} will be set to {@link Activity#RESULT_OK} and the 259 * {@code data} {@link Intent} will contain {@link AssociationInfo} extra named 260 * {@link #EXTRA_ASSOCIATION}. 261 * <pre> 262 * <code> 263 * if (resultCode == Activity.RESULT_OK) { 264 * AssociationInfo associationInfo = data.getParcelableExtra(EXTRA_ASSOCIATION); 265 * } 266 * </code> 267 * </pre> 268 * </p> 269 * 270 * <p> 271 * If the Companion Device Manager Service is not able to create an association, it will 272 * invoke {@link Callback#onFailure(CharSequence)}. 273 * 274 * If this happened after the application has launched the UI (eg. the user chose to reject 275 * the association), the outcome will also be delivered to the applications via 276 * {@link Activity#setResult(int)} with the {@link Activity#RESULT_CANCELED} 277 * {@code resultCode}. 278 * </p> 279 * 280 * <p> 281 * Note that in some cases the Companion Device Manager Service may not need to collect 282 * user's approval for creating an association. In such cases, this method will not be 283 * invoked, and {@link #onAssociationCreated(AssociationInfo)} may be invoked right away. 284 * </p> 285 * 286 * @see #associate(AssociationRequest, Executor, Callback) 287 * @see #associate(AssociationRequest, Callback, Handler) 288 * @see #EXTRA_ASSOCIATION 289 */ 290 public abstract static class Callback { 291 /** 292 * @deprecated method was renamed to onAssociationPending() to provide better clarity; both 293 * methods are functionally equivalent and only one needs to be overridden. 294 * 295 * @see #onAssociationPending(IntentSender) 296 */ 297 @Deprecated onDeviceFound(@onNull IntentSender intentSender)298 public void onDeviceFound(@NonNull IntentSender intentSender) {} 299 300 /** 301 * Invoked when the association needs to approved by the user. 302 * 303 * Applications should launch the {@link Activity} "encapsulated" in {@code intentSender} 304 * {@link IntentSender} object by calling 305 * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}. 306 * 307 * @param intentSender an {@link IntentSender} which applications should use to launch 308 * the UI for the user to confirm the association. 309 */ onAssociationPending(@onNull IntentSender intentSender)310 public void onAssociationPending(@NonNull IntentSender intentSender) { 311 onDeviceFound(intentSender); 312 } 313 314 /** 315 * Invoked when the association is created. 316 * 317 * @param associationInfo contains details of the newly-established association. 318 */ onAssociationCreated(@onNull AssociationInfo associationInfo)319 public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {} 320 321 /** 322 * Invoked if the association could not be created. 323 * 324 * @param error error message. 325 */ onFailure(@ullable CharSequence error)326 public abstract void onFailure(@Nullable CharSequence error); 327 } 328 329 private final ICompanionDeviceManager mService; 330 private Context mContext; 331 332 @GuardedBy("mListeners") 333 private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>(); 334 335 @GuardedBy("mTransports") 336 private final SparseArray<Transport> mTransports = new SparseArray<>(); 337 338 /** @hide */ CompanionDeviceManager( @ullable ICompanionDeviceManager service, @NonNull Context context)339 public CompanionDeviceManager( 340 @Nullable ICompanionDeviceManager service, @NonNull Context context) { 341 mService = service; 342 mContext = context; 343 } 344 345 /** 346 * Request to associate this app with a companion device. 347 * 348 * <p>Note that before creating establishing association the system may need to show UI to 349 * collect user confirmation.</p> 350 * 351 * <p>If the app needs to be excluded from battery optimizations (run in the background) 352 * or to have unrestricted data access (use data in the background) it should declare use of 353 * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and 354 * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its 355 * AndroidManifest.xml respectively. 356 * Note that these special capabilities have a negative effect on the device's battery and 357 * user's data usage, therefore you should request them when absolutely necessary.</p> 358 * 359 * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently 360 * {@link AssociationInfo} objects, that represent their existing associations. 361 * Applications can also use {@link #disassociate(int)} to remove an association, and are 362 * recommended to do when an association is no longer relevant to avoid unnecessary battery 363 * and/or data drain resulting from special privileges that the association provides</p> 364 * 365 * <p>Calling this API requires a uses-feature 366 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 367 ** 368 * @param request A request object that describes details of the request. 369 * @param callback The callback used to notify application when the association is created. 370 * @param handler The handler which will be used to invoke the callback. 371 * 372 * @see AssociationRequest.Builder 373 * @see #getMyAssociations() 374 * @see #disassociate(int) 375 * @see #associate(AssociationRequest, Executor, Callback) 376 */ 377 @UserHandleAware 378 @RequiresPermission(anyOf = { 379 REQUEST_COMPANION_PROFILE_WATCH, 380 REQUEST_COMPANION_PROFILE_COMPUTER, 381 REQUEST_COMPANION_PROFILE_APP_STREAMING, 382 REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION, 383 }, conditional = true) associate( @onNull AssociationRequest request, @NonNull Callback callback, @Nullable Handler handler)384 public void associate( 385 @NonNull AssociationRequest request, 386 @NonNull Callback callback, 387 @Nullable Handler handler) { 388 if (!checkFeaturePresent()) return; 389 Objects.requireNonNull(request, "Request cannot be null"); 390 Objects.requireNonNull(callback, "Callback cannot be null"); 391 handler = Handler.mainIfNull(handler); 392 393 try { 394 mService.associate(request, new AssociationRequestCallbackProxy(handler, callback), 395 mContext.getOpPackageName(), mContext.getUserId()); 396 } catch (RemoteException e) { 397 throw e.rethrowFromSystemServer(); 398 } 399 } 400 401 /** 402 * Request to associate this app with a companion device. 403 * 404 * <p>Note that before creating establishing association the system may need to show UI to 405 * collect user confirmation.</p> 406 * 407 * <p>If the app needs to be excluded from battery optimizations (run in the background) 408 * or to have unrestricted data access (use data in the background) it should declare use of 409 * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and 410 * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its 411 * AndroidManifest.xml respectively. 412 * Note that these special capabilities have a negative effect on the device's battery and 413 * user's data usage, therefore you should request them when absolutely necessary.</p> 414 * 415 * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently 416 * {@link AssociationInfo} objects, that represent their existing associations. 417 * Applications can also use {@link #disassociate(int)} to remove an association, and are 418 * recommended to do when an association is no longer relevant to avoid unnecessary battery 419 * and/or data drain resulting from special privileges that the association provides</p> 420 * 421 * <p>Note that if you use this api to associate with a Bluetooth device, please make sure 422 * to cancel your own Bluetooth discovery before calling this api, otherwise the callback 423 * may fail to return the desired device.</p> 424 * 425 * <p>Calling this API requires a uses-feature 426 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 427 ** 428 * @param request A request object that describes details of the request. 429 * @param executor The executor which will be used to invoke the callback. 430 * @param callback The callback used to notify application when the association is created. 431 * 432 * @see AssociationRequest.Builder 433 * @see #getMyAssociations() 434 * @see #disassociate(int) 435 * @see BluetoothAdapter#cancelDiscovery() 436 */ 437 @UserHandleAware 438 @RequiresPermission(anyOf = { 439 REQUEST_COMPANION_PROFILE_WATCH, 440 REQUEST_COMPANION_PROFILE_COMPUTER, 441 REQUEST_COMPANION_PROFILE_APP_STREAMING, 442 REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION 443 }, conditional = true) associate( @onNull AssociationRequest request, @NonNull Executor executor, @NonNull Callback callback)444 public void associate( 445 @NonNull AssociationRequest request, 446 @NonNull Executor executor, 447 @NonNull Callback callback) { 448 if (!checkFeaturePresent()) return; 449 Objects.requireNonNull(request, "Request cannot be null"); 450 Objects.requireNonNull(executor, "Executor cannot be null"); 451 Objects.requireNonNull(callback, "Callback cannot be null"); 452 453 try { 454 mService.associate(request, new AssociationRequestCallbackProxy(executor, callback), 455 mContext.getOpPackageName(), mContext.getUserId()); 456 } catch (RemoteException e) { 457 throw e.rethrowFromSystemServer(); 458 } 459 } 460 461 /** 462 * Cancel the current association activity. 463 * 464 * <p>The app should launch the returned {@code intentSender} by calling 465 * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to 466 * cancel the current association activity</p> 467 * 468 * <p>Calling this API requires a uses-feature 469 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 470 * 471 * @return An {@link IntentSender} that the app should use to launch in order to cancel the 472 * current association activity 473 */ 474 @UserHandleAware 475 @Nullable buildAssociationCancellationIntent()476 public IntentSender buildAssociationCancellationIntent() { 477 if (!checkFeaturePresent()) return null; 478 479 try { 480 PendingIntent pendingIntent = mService.buildAssociationCancellationIntent( 481 mContext.getOpPackageName(), mContext.getUserId()); 482 return pendingIntent.getIntentSender(); 483 } catch (RemoteException e) { 484 throw e.rethrowFromSystemServer(); 485 } 486 } 487 488 /** 489 * <p>Enable system data sync (it only supports call metadata sync for now). 490 * By default all supported system data types are enabled.</p> 491 * 492 * <p>Calling this API requires a uses-feature 493 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 494 * 495 * @param associationId id of the device association. 496 * @param flags system data types to be enabled. 497 */ enableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags)498 public void enableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) { 499 if (!checkFeaturePresent()) { 500 return; 501 } 502 503 try { 504 mService.enableSystemDataSync(associationId, flags); 505 } catch (RemoteException e) { 506 throw e.rethrowFromSystemServer(); 507 } 508 } 509 510 /** 511 * <p>Disable system data sync (it only supports call metadata sync for now). 512 * By default all supported system data types are enabled.</p> 513 * 514 * <p>Calling this API requires a uses-feature 515 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 516 * 517 * @param associationId id of the device association. 518 * @param flags system data types to be disabled. 519 */ disableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags)520 public void disableSystemDataSyncForTypes(int associationId, @DataSyncTypes int flags) { 521 if (!checkFeaturePresent()) { 522 return; 523 } 524 525 try { 526 mService.disableSystemDataSync(associationId, flags); 527 } catch (RemoteException e) { 528 throw e.rethrowFromSystemServer(); 529 } 530 } 531 532 /** 533 * @hide 534 */ enablePermissionsSync(int associationId)535 public void enablePermissionsSync(int associationId) { 536 try { 537 mService.enablePermissionsSync(associationId); 538 } catch (RemoteException e) { 539 throw e.rethrowFromSystemServer(); 540 } 541 } 542 543 /** 544 * @hide 545 */ disablePermissionsSync(int associationId)546 public void disablePermissionsSync(int associationId) { 547 try { 548 mService.disablePermissionsSync(associationId); 549 } catch (RemoteException e) { 550 throw e.rethrowFromSystemServer(); 551 } 552 } 553 554 /** 555 * @hide 556 */ getPermissionSyncRequest(int associationId)557 public PermissionSyncRequest getPermissionSyncRequest(int associationId) { 558 try { 559 return mService.getPermissionSyncRequest(associationId); 560 } catch (RemoteException e) { 561 throw e.rethrowFromSystemServer(); 562 } 563 } 564 565 /** 566 * <p>Calling this API requires a uses-feature 567 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 568 * 569 * @return a list of MAC addresses of devices that have been previously associated with the 570 * current app are managed by CompanionDeviceManager (ie. does not include devices managed by 571 * application itself even if they have a MAC address). 572 * 573 * @deprecated use {@link #getMyAssociations()} 574 */ 575 @Deprecated 576 @UserHandleAware 577 @NonNull getAssociations()578 public List<String> getAssociations() { 579 return CollectionUtils.mapNotNull(getMyAssociations(), 580 a -> a.isSelfManaged() ? null : a.getDeviceMacAddressAsString()); 581 } 582 583 /** 584 * <p>Calling this API requires a uses-feature 585 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 586 * 587 * @return a list of associations that have been previously associated with the current app. 588 */ 589 @UserHandleAware 590 @NonNull getMyAssociations()591 public List<AssociationInfo> getMyAssociations() { 592 if (!checkFeaturePresent()) return Collections.emptyList(); 593 594 try { 595 return mService.getAssociations(mContext.getOpPackageName(), mContext.getUserId()); 596 } catch (RemoteException e) { 597 throw e.rethrowFromSystemServer(); 598 } 599 } 600 601 /** 602 * Remove the association between this app and the device with the given mac address. 603 * 604 * <p>Any privileges provided via being associated with a given device will be revoked</p> 605 * 606 * <p>Consider doing so when the 607 * association is no longer relevant to avoid unnecessary battery and/or data drain resulting 608 * from special privileges that the association provides</p> 609 * 610 * <p>Calling this API requires a uses-feature 611 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 612 * 613 * @param deviceMacAddress the MAC address of device to disassociate from this app. Device 614 * address is case-sensitive in API level < 33. 615 * 616 * @deprecated use {@link #disassociate(int)} 617 */ 618 @UserHandleAware 619 @Deprecated disassociate(@onNull String deviceMacAddress)620 public void disassociate(@NonNull String deviceMacAddress) { 621 if (!checkFeaturePresent()) return; 622 623 try { 624 mService.legacyDisassociate(deviceMacAddress, mContext.getOpPackageName(), 625 mContext.getUserId()); 626 } catch (RemoteException e) { 627 throw e.rethrowFromSystemServer(); 628 } 629 } 630 631 /** 632 * Remove an association. 633 * 634 * <p>Any privileges provided via being associated with a given device will be revoked</p> 635 * 636 * <p>Calling this API requires a uses-feature 637 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 638 * 639 * @param associationId id of the association to be removed. 640 * 641 * @see #associate(AssociationRequest, Executor, Callback) 642 * @see AssociationInfo#getId() 643 */ 644 @UserHandleAware disassociate(int associationId)645 public void disassociate(int associationId) { 646 if (!checkFeaturePresent()) return; 647 648 try { 649 mService.disassociate(associationId); 650 } catch (RemoteException e) { 651 throw e.rethrowFromSystemServer(); 652 } 653 } 654 655 /** 656 * Request notification access for the given component. 657 * 658 * The given component must follow the protocol specified in {@link NotificationListenerService} 659 * 660 * Only components from the same {@link ComponentName#getPackageName package} as the calling app 661 * are allowed. 662 * 663 * Your app must have an association with a device before calling this API 664 * 665 * <p>Calling this API requires a uses-feature 666 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 667 */ 668 @UserHandleAware requestNotificationAccess(ComponentName component)669 public void requestNotificationAccess(ComponentName component) { 670 if (!checkFeaturePresent()) { 671 return; 672 } 673 try { 674 IntentSender intentSender = mService 675 .requestNotificationAccess(component, mContext.getUserId()) 676 .getIntentSender(); 677 mContext.startIntentSender(intentSender, null, 0, 0, 0); 678 } catch (RemoteException e) { 679 throw e.rethrowFromSystemServer(); 680 } catch (IntentSender.SendIntentException e) { 681 throw new RuntimeException(e); 682 } 683 } 684 685 /** 686 * Check whether the given component can access the notifications via a 687 * {@link NotificationListenerService} 688 * 689 * Your app must have an association with a device before calling this API 690 * 691 * <p>Calling this API requires a uses-feature 692 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> 693 * 694 * @param component the name of the component 695 * @return whether the given component has the notification listener permission 696 * 697 * @deprecated Use 698 * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)} instead. 699 */ 700 @Deprecated hasNotificationAccess(ComponentName component)701 public boolean hasNotificationAccess(ComponentName component) { 702 if (!checkFeaturePresent()) { 703 return false; 704 } 705 try { 706 return mService.hasNotificationAccess(component); 707 } catch (RemoteException e) { 708 throw e.rethrowFromSystemServer(); 709 } 710 } 711 712 /** 713 * Check if a given package was {@link #associate associated} with a device with given 714 * Wi-Fi MAC address for a given user. 715 * 716 * <p>This is a system API protected by the 717 * {@link android.Manifest.permission#MANAGE_COMPANION_DEVICES} permission, that’s currently 718 * called by the Android Wi-Fi stack to determine whether user consent is required to connect 719 * to a Wi-Fi network. Devices that have been pre-registered as companion devices will not 720 * require user consent to connect.</p> 721 * 722 * <p>Note if the caller has the 723 * {@link android.Manifest.permission#COMPANION_APPROVE_WIFI_CONNECTIONS} permission, this 724 * method will return true by default.</p> 725 * 726 * @param packageName the name of the package that has the association with the companion device 727 * @param macAddress the Wi-Fi MAC address or BSSID of the companion device to check for 728 * @param user the user handle that currently hosts the package being queried for a companion 729 * device association 730 * @return whether a corresponding association record exists 731 * 732 * @hide 733 */ 734 @SystemApi 735 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) isDeviceAssociatedForWifiConnection( @onNull String packageName, @NonNull MacAddress macAddress, @NonNull UserHandle user)736 public boolean isDeviceAssociatedForWifiConnection( 737 @NonNull String packageName, 738 @NonNull MacAddress macAddress, 739 @NonNull UserHandle user) { 740 if (!checkFeaturePresent()) return false; 741 Objects.requireNonNull(packageName, "package name cannot be null"); 742 Objects.requireNonNull(macAddress, "mac address cannot be null"); 743 Objects.requireNonNull(user, "user cannot be null"); 744 try { 745 return mService.isDeviceAssociatedForWifiConnection( 746 packageName, macAddress.toString(), user.getIdentifier()); 747 } catch (RemoteException e) { 748 throw e.rethrowFromSystemServer(); 749 } 750 } 751 752 /** 753 * Gets all package-device {@link AssociationInfo}s for the current user. 754 * 755 * @return the associations list 756 * @see #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener) 757 * @see #removeOnAssociationsChangedListener(OnAssociationsChangedListener) 758 * @hide 759 */ 760 @SystemApi 761 @UserHandleAware 762 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) getAllAssociations()763 public @NonNull List<AssociationInfo> getAllAssociations() { 764 if (!checkFeaturePresent()) return Collections.emptyList(); 765 try { 766 return mService.getAllAssociationsForUser(mContext.getUserId()); 767 } catch (RemoteException e) { 768 throw e.rethrowFromSystemServer(); 769 } 770 } 771 772 /** 773 * Listener for any changes to {@link AssociationInfo}. 774 * 775 * @hide 776 */ 777 @SystemApi 778 public interface OnAssociationsChangedListener { 779 /** 780 * Invoked when a change occurs to any of the associations for the user (including adding 781 * new associations and removing existing associations). 782 * 783 * @param associations all existing associations for the user (after the change). 784 */ onAssociationsChanged(@onNull List<AssociationInfo> associations)785 void onAssociationsChanged(@NonNull List<AssociationInfo> associations); 786 } 787 788 /** 789 * Register listener for any changes to {@link AssociationInfo}. 790 * 791 * @see #getAllAssociations() 792 * @hide 793 */ 794 @SystemApi 795 @UserHandleAware 796 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) addOnAssociationsChangedListener( @onNull Executor executor, @NonNull OnAssociationsChangedListener listener)797 public void addOnAssociationsChangedListener( 798 @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) { 799 if (!checkFeaturePresent()) return; 800 synchronized (mListeners) { 801 final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy( 802 executor, listener); 803 try { 804 mService.addOnAssociationsChangedListener(proxy, mContext.getUserId()); 805 } catch (RemoteException e) { 806 throw e.rethrowFromSystemServer(); 807 } 808 mListeners.add(proxy); 809 } 810 } 811 812 /** 813 * Unregister listener for any changes to {@link AssociationInfo}. 814 * 815 * @see #getAllAssociations() 816 * @hide 817 */ 818 @SystemApi 819 @UserHandleAware 820 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) removeOnAssociationsChangedListener( @onNull OnAssociationsChangedListener listener)821 public void removeOnAssociationsChangedListener( 822 @NonNull OnAssociationsChangedListener listener) { 823 if (!checkFeaturePresent()) return; 824 synchronized (mListeners) { 825 final Iterator<OnAssociationsChangedListenerProxy> iterator = mListeners.iterator(); 826 while (iterator.hasNext()) { 827 final OnAssociationsChangedListenerProxy proxy = iterator.next(); 828 if (proxy.mListener == listener) { 829 try { 830 mService.removeOnAssociationsChangedListener(proxy, mContext.getUserId()); 831 } catch (RemoteException e) { 832 throw e.rethrowFromSystemServer(); 833 } 834 iterator.remove(); 835 } 836 } 837 } 838 } 839 840 /** 841 * Listener for any changes to {@link com.android.server.companion.transport.Transport}. 842 * 843 * @hide 844 */ 845 public interface OnTransportsChangedListener { 846 /** 847 * Invoked when a change occurs to any of the transports 848 * 849 * @param associations all the associations which have connected transports 850 */ onTransportsChanged(@onNull List<AssociationInfo> associations)851 void onTransportsChanged(@NonNull List<AssociationInfo> associations); 852 } 853 854 /** 855 * Register a listener for any changes to 856 * {@link com.android.server.companion.transport.Transport}. Your app will receive a callback to 857 * {@link OnTransportsChangedListener} immediately with all the existing transports. 858 * 859 * @hide 860 */ addOnTransportsChangedListener( @onNull Executor executor, @NonNull OnTransportsChangedListener listener)861 public void addOnTransportsChangedListener( 862 @NonNull Executor executor, @NonNull OnTransportsChangedListener listener) { 863 final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy( 864 executor, listener); 865 try { 866 mService.addOnTransportsChangedListener(proxy); 867 } catch (RemoteException e) { 868 throw e.rethrowFromSystemServer(); 869 } 870 } 871 872 /** 873 * Unregister a listener to stop receiving any changes to 874 * {@link com.android.server.companion.transport.Transport}. 875 * 876 * @hide 877 */ removeOnTransportsChangedListener( @onNull OnTransportsChangedListener listener)878 public void removeOnTransportsChangedListener( 879 @NonNull OnTransportsChangedListener listener) { 880 final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy( 881 null, listener); 882 try { 883 mService.removeOnTransportsChangedListener(proxy); 884 } catch (RemoteException e) { 885 throw e.rethrowFromSystemServer(); 886 } 887 } 888 889 /** 890 * Send a message to remote devices 891 * 892 * @hide 893 */ sendMessage(int messageType, byte[] data, int[] associationIds)894 public void sendMessage(int messageType, byte[] data, int[] associationIds) { 895 try { 896 mService.sendMessage(messageType, data, associationIds); 897 } catch (RemoteException e) { 898 throw e.rethrowFromSystemServer(); 899 } 900 } 901 902 /** 903 * Listener when a message is received for the registered message type 904 * 905 * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener) 906 * 907 * @hide 908 */ 909 public interface OnMessageReceivedListener { 910 /** 911 * Called when a message is received 912 */ onMessageReceived(int associationId, byte[] data)913 void onMessageReceived(int associationId, byte[] data); 914 } 915 916 /** 917 * Register a listener to receive callbacks when a message is received by the given type 918 * 919 * @see com.android.server.companion.transport.Transport for supported message types 920 * 921 * @hide 922 */ addOnMessageReceivedListener(@onNull Executor executor, int messageType, OnMessageReceivedListener listener)923 public void addOnMessageReceivedListener(@NonNull Executor executor, int messageType, 924 OnMessageReceivedListener listener) { 925 final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy( 926 executor, listener); 927 try { 928 mService.addOnMessageReceivedListener(messageType, proxy); 929 } catch (RemoteException e) { 930 throw e.rethrowFromSystemServer(); 931 } 932 } 933 934 /** 935 * Unregister a listener to stop receiving callbacks when a message is received by the given 936 * type 937 * 938 * @see com.android.server.companion.transport.Transport for supported message types 939 * 940 * @hide 941 */ removeOnMessageReceivedListener(int messageType, OnMessageReceivedListener listener)942 public void removeOnMessageReceivedListener(int messageType, 943 OnMessageReceivedListener listener) { 944 final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy( 945 null, listener); 946 try { 947 mService.removeOnMessageReceivedListener(messageType, proxy); 948 } catch (RemoteException e) { 949 throw e.rethrowFromSystemServer(); 950 } 951 } 952 953 /** 954 * Checks whether the bluetooth device represented by the mac address was recently associated 955 * with the companion app. This allows these devices to skip the Bluetooth pairing dialog if 956 * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}. 957 * 958 * @param packageName the package name of the calling app 959 * @param deviceMacAddress the bluetooth device's mac address 960 * @param user the user handle that currently hosts the package being queried for a companion 961 * device association 962 * @return true if it was recently associated and we can bypass the dialog, false otherwise 963 * @hide 964 */ 965 @SystemApi 966 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) canPairWithoutPrompt(@onNull String packageName, @NonNull String deviceMacAddress, @NonNull UserHandle user)967 public boolean canPairWithoutPrompt(@NonNull String packageName, 968 @NonNull String deviceMacAddress, @NonNull UserHandle user) { 969 if (!checkFeaturePresent()) { 970 return false; 971 } 972 Objects.requireNonNull(packageName, "package name cannot be null"); 973 Objects.requireNonNull(deviceMacAddress, "device mac address cannot be null"); 974 Objects.requireNonNull(user, "user handle cannot be null"); 975 try { 976 return mService.canPairWithoutPrompt(packageName, deviceMacAddress, 977 user.getIdentifier()); 978 } catch (RemoteException e) { 979 throw e.rethrowFromSystemServer(); 980 } 981 } 982 983 /** 984 * Register to receive callbacks whenever the associated device comes in and out of range. 985 * 986 * <p>The provided device must be {@link #associate associated} with the calling app before 987 * calling this method.</p> 988 * 989 * <p>Caller must implement a single {@link CompanionDeviceService} which will be bound to and 990 * receive callbacks to {@link CompanionDeviceService#onDeviceAppeared} and 991 * {@link CompanionDeviceService#onDeviceDisappeared}. 992 * The app doesn't need to remain running in order to receive its callbacks.</p> 993 * 994 * <p>Calling app must declare uses-permission 995 * {@link android.Manifest.permission#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE}.</p> 996 * 997 * <p>Calling app must check for feature presence of 998 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.</p> 999 * 1000 * <p>For Bluetooth LE devices, this is based on scanning for device with the given address. 1001 * The system will scan for the device when Bluetooth is ON or Bluetooth scanning is ON.</p> 1002 * 1003 * <p>For Bluetooth classic devices this is triggered when the device connects/disconnects. 1004 * WiFi devices are not supported.</p> 1005 * 1006 * <p>If a Bluetooth LE device wants to use a rotating mac address, it is recommended to use 1007 * Resolvable Private Address, and ensure the device is bonded to the phone so that android OS 1008 * is able to resolve the address.</p> 1009 * 1010 * @param deviceAddress a previously-associated companion device's address 1011 * 1012 * @throws DeviceNotAssociatedException if the given device was not previously associated 1013 * with this app. 1014 */ 1015 @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) startObservingDevicePresence(@onNull String deviceAddress)1016 public void startObservingDevicePresence(@NonNull String deviceAddress) 1017 throws DeviceNotAssociatedException { 1018 if (!checkFeaturePresent()) { 1019 return; 1020 } 1021 Objects.requireNonNull(deviceAddress, "address cannot be null"); 1022 try { 1023 mService.registerDevicePresenceListenerService(deviceAddress, 1024 mContext.getOpPackageName(), mContext.getUserId()); 1025 } catch (RemoteException e) { 1026 ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); 1027 throw e.rethrowFromSystemServer(); 1028 } 1029 int callingUid = Binder.getCallingUid(); 1030 int callingPid = Binder.getCallingPid(); 1031 ActivityManagerInternal managerInternal = 1032 LocalServices.getService(ActivityManagerInternal.class); 1033 if (managerInternal != null) { 1034 managerInternal 1035 .logFgsApiBegin(ActivityManager.FOREGROUND_SERVICE_API_TYPE_CDM, 1036 callingUid, callingPid); 1037 } 1038 } 1039 1040 /** 1041 * Unregister for receiving callbacks whenever the associated device comes in and out of range. 1042 * 1043 * The provided device must be {@link #associate associated} with the calling app before 1044 * calling this method. 1045 * 1046 * Calling app must declare uses-permission 1047 * {@link android.Manifest.permission#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE}. 1048 * 1049 * Calling app must check for feature presence of 1050 * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API. 1051 * 1052 * @param deviceAddress a previously-associated companion device's address 1053 * 1054 * @throws DeviceNotAssociatedException if the given device was not previously associated 1055 * with this app. 1056 */ 1057 @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) stopObservingDevicePresence(@onNull String deviceAddress)1058 public void stopObservingDevicePresence(@NonNull String deviceAddress) 1059 throws DeviceNotAssociatedException { 1060 if (!checkFeaturePresent()) { 1061 return; 1062 } 1063 Objects.requireNonNull(deviceAddress, "address cannot be null"); 1064 try { 1065 mService.unregisterDevicePresenceListenerService(deviceAddress, 1066 mContext.getPackageName(), mContext.getUserId()); 1067 } catch (RemoteException e) { 1068 ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); 1069 } 1070 int callingUid = Binder.getCallingUid(); 1071 int callingPid = Binder.getCallingPid(); 1072 ActivityManagerInternal managerInternal = 1073 LocalServices.getService(ActivityManagerInternal.class); 1074 if (managerInternal != null) { 1075 managerInternal 1076 .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_CDM, 1077 callingUid, callingPid); 1078 } 1079 } 1080 1081 /** 1082 * Dispatch a message to system for processing. It should only be called by 1083 * {@link CompanionDeviceService#dispatchMessageToSystem(int, int, byte[])} 1084 * 1085 * <p>Calling app must declare uses-permission 1086 * {@link android.Manifest.permission#DELIVER_COMPANION_MESSAGES}</p> 1087 * 1088 * @param messageId id of the message 1089 * @param associationId association id of the associated device where data is coming from 1090 * @param message message received from the associated device 1091 * 1092 * @throws DeviceNotAssociatedException if the given device was not previously associated with 1093 * this app 1094 * 1095 * @hide 1096 */ 1097 @Deprecated 1098 @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) dispatchMessage(int messageId, int associationId, @NonNull byte[] message)1099 public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) 1100 throws DeviceNotAssociatedException { 1101 Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport"); 1102 } 1103 1104 /** 1105 * Attach a bidirectional communication stream to be used as a transport channel for 1106 * transporting system data between associated devices. 1107 * 1108 * @param associationId id of the associated device. 1109 * @param in Already connected stream of data incoming from remote 1110 * associated device. 1111 * @param out Already connected stream of data outgoing to remote associated 1112 * device. 1113 * @throws DeviceNotAssociatedException Thrown if the associationId was not previously 1114 * associated with this app. 1115 * 1116 * @see #buildPermissionTransferUserConsentIntent(int) 1117 * @see #startSystemDataTransfer(int, Executor, OutcomeReceiver) 1118 * @see #detachSystemDataTransport(int) 1119 */ 1120 @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) attachSystemDataTransport(int associationId, @NonNull InputStream in, @NonNull OutputStream out)1121 public void attachSystemDataTransport(int associationId, @NonNull InputStream in, 1122 @NonNull OutputStream out) throws DeviceNotAssociatedException { 1123 synchronized (mTransports) { 1124 if (mTransports.contains(associationId)) { 1125 detachSystemDataTransport(associationId); 1126 } 1127 1128 try { 1129 final Transport transport = new Transport(associationId, in, out); 1130 mTransports.put(associationId, transport); 1131 transport.start(); 1132 } catch (IOException e) { 1133 throw new RuntimeException("Failed to attach transport", e); 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Detach the transport channel that's previously attached for the associated device. The system 1140 * will stop transferring any system data when this method is called. 1141 * 1142 * @param associationId id of the associated device. 1143 * @throws DeviceNotAssociatedException Thrown if the associationId was not previously 1144 * associated with this app. 1145 * 1146 * @see #attachSystemDataTransport(int, InputStream, OutputStream) 1147 */ 1148 @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) detachSystemDataTransport(int associationId)1149 public void detachSystemDataTransport(int associationId) 1150 throws DeviceNotAssociatedException { 1151 synchronized (mTransports) { 1152 final Transport transport = mTransports.get(associationId); 1153 if (transport != null) { 1154 mTransports.delete(associationId); 1155 transport.stop(); 1156 } 1157 } 1158 } 1159 1160 /** 1161 * Associates given device with given app for the given user directly, without UI prompt. 1162 * 1163 * @param packageName package name of the companion app 1164 * @param macAddress mac address of the device to associate 1165 * @param certificate The SHA256 digest of the companion app's signing certificate 1166 * 1167 * @hide 1168 */ 1169 @SystemApi 1170 @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) associate( @onNull String packageName, @NonNull MacAddress macAddress, @NonNull byte[] certificate)1171 public void associate( 1172 @NonNull String packageName, 1173 @NonNull MacAddress macAddress, 1174 @NonNull byte[] certificate) { 1175 if (!checkFeaturePresent()) { 1176 return; 1177 } 1178 Objects.requireNonNull(packageName, "package name cannot be null"); 1179 Objects.requireNonNull(macAddress, "mac address cannot be null"); 1180 1181 UserHandle user = android.os.Process.myUserHandle(); 1182 try { 1183 mService.createAssociation( 1184 packageName, macAddress.toString(), user.getIdentifier(), certificate); 1185 } catch (RemoteException e) { 1186 throw e.rethrowFromSystemServer(); 1187 } 1188 } 1189 1190 /** 1191 * Notify the system that the given self-managed association has just appeared. 1192 * This causes the system to bind to the companion app to keep it running until the association 1193 * is reported as disappeared 1194 * 1195 * <p>This API is only available for the companion apps that manage the connectivity by 1196 * themselves.</p> 1197 * 1198 * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association 1199 * recorded by CompanionDeviceManager 1200 * 1201 * @hide 1202 */ 1203 @SystemApi 1204 @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) notifyDeviceAppeared(int associationId)1205 public void notifyDeviceAppeared(int associationId) { 1206 try { 1207 mService.notifyDeviceAppeared(associationId); 1208 } catch (RemoteException e) { 1209 throw e.rethrowFromSystemServer(); 1210 } 1211 } 1212 1213 /** 1214 * Notify the system that the given self-managed association has just disappeared. 1215 * This causes the system to unbind to the companion app. 1216 * 1217 * <p>This API is only available for the companion apps that manage the connectivity by 1218 * themselves.</p> 1219 * 1220 * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association 1221 * recorded by CompanionDeviceManager 1222 1223 * @hide 1224 */ 1225 @SystemApi 1226 @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) notifyDeviceDisappeared(int associationId)1227 public void notifyDeviceDisappeared(int associationId) { 1228 try { 1229 mService.notifyDeviceDisappeared(associationId); 1230 } catch (RemoteException e) { 1231 throw e.rethrowFromSystemServer(); 1232 } 1233 } 1234 1235 /** 1236 * Build a permission sync user consent dialog. 1237 * 1238 * <p>Only the companion app which owns the association can call this method. Otherwise a null 1239 * IntentSender will be returned from this method and an error will be logged. 1240 * The app should launch the {@link Activity} in the returned {@code intentSender} 1241 * {@link IntentSender} by calling 1242 * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}.</p> 1243 * 1244 * <p>The permission transfer doesn't happen immediately after the call or when the user 1245 * consents. The app needs to call 1246 * {@link #attachSystemDataTransport(int, InputStream, OutputStream)} to attach a transport 1247 * channel and 1248 * {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} to trigger the system data 1249 * transfer}.</p> 1250 * 1251 * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association 1252 * of the companion device recorded by CompanionDeviceManager 1253 * @return An {@link IntentSender} that the app should use to launch the UI for 1254 * the user to confirm the system data transfer request. 1255 * 1256 * @see #attachSystemDataTransport(int, InputStream, OutputStream) 1257 * @see #startSystemDataTransfer(int, Executor, OutcomeReceiver) 1258 */ 1259 @UserHandleAware 1260 @Nullable buildPermissionTransferUserConsentIntent(int associationId)1261 public IntentSender buildPermissionTransferUserConsentIntent(int associationId) 1262 throws DeviceNotAssociatedException { 1263 try { 1264 PendingIntent pendingIntent = mService.buildPermissionTransferUserConsentIntent( 1265 mContext.getOpPackageName(), 1266 mContext.getUserId(), 1267 associationId); 1268 if (pendingIntent == null) { 1269 return null; 1270 } 1271 return pendingIntent.getIntentSender(); 1272 } catch (RemoteException e) { 1273 ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); 1274 throw e.rethrowFromSystemServer(); 1275 } 1276 } 1277 1278 /** 1279 * Start system data transfer which has been previously approved by the user. 1280 * 1281 * <p>Before calling this method, the app needs to make sure there's a communication channel 1282 * between two devices, and has prompted user consent dialogs built by one of these methods: 1283 * {@link #buildPermissionTransferUserConsentIntent(int)}. 1284 * The transfer may fail if the communication channel is disconnected during the transfer.</p> 1285 * 1286 * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association 1287 * of the companion device recorded by CompanionDeviceManager 1288 * @throws DeviceNotAssociatedException Exception if the companion device is not associated 1289 * @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead. 1290 * @hide 1291 */ 1292 @Deprecated 1293 @UserHandleAware startSystemDataTransfer(int associationId)1294 public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException { 1295 try { 1296 mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(), 1297 associationId, null); 1298 } catch (RemoteException e) { 1299 ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); 1300 throw e.rethrowFromSystemServer(); 1301 } 1302 } 1303 1304 /** 1305 * Start system data transfer which has been previously approved by the user. 1306 * 1307 * <p>Before calling this method, the app needs to make sure 1308 * {@link #attachSystemDataTransport(int, InputStream, OutputStream) the transport channel is 1309 * attached}, and 1310 * {@link #buildPermissionTransferUserConsentIntent(int) the user consent dialog has prompted to 1311 * the user}. 1312 * The transfer will fail if the transport channel is disconnected or 1313 * {@link #detachSystemDataTransport(int) detached} during the transfer.</p> 1314 * 1315 * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association 1316 * of the companion device recorded by CompanionDeviceManager 1317 * @param executor The executor which will be used to invoke the result callback. 1318 * @param result The callback to notify the app of the result of the system data transfer. 1319 * @throws DeviceNotAssociatedException Exception if the companion device is not associated 1320 */ 1321 @UserHandleAware startSystemDataTransfer( int associationId, @NonNull Executor executor, @NonNull OutcomeReceiver<Void, CompanionException> result)1322 public void startSystemDataTransfer( 1323 int associationId, 1324 @NonNull Executor executor, 1325 @NonNull OutcomeReceiver<Void, CompanionException> result) 1326 throws DeviceNotAssociatedException { 1327 try { 1328 mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(), 1329 associationId, new SystemDataTransferCallbackProxy(executor, result)); 1330 } catch (RemoteException e) { 1331 ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); 1332 throw e.rethrowFromSystemServer(); 1333 } 1334 } 1335 1336 /** 1337 * Checks whether the calling companion application is currently bound. 1338 * 1339 * @return true if application is bound, false otherwise 1340 * @hide 1341 */ 1342 @UserHandleAware isCompanionApplicationBound()1343 public boolean isCompanionApplicationBound() { 1344 try { 1345 return mService.isCompanionApplicationBound( 1346 mContext.getOpPackageName(), mContext.getUserId()); 1347 } catch (RemoteException e) { 1348 throw e.rethrowFromSystemServer(); 1349 } 1350 } 1351 1352 /** 1353 * Enable or disable secure transport for testing. Defaults to enabled. 1354 * Should not be used outside of testing. 1355 * 1356 * @param enabled true to enable. false to disable. 1357 * @hide 1358 */ 1359 @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) enableSecureTransport(boolean enabled)1360 public void enableSecureTransport(boolean enabled) { 1361 try { 1362 mService.enableSecureTransport(enabled); 1363 } catch (RemoteException e) { 1364 throw e.rethrowFromSystemServer(); 1365 } 1366 } 1367 checkFeaturePresent()1368 private boolean checkFeaturePresent() { 1369 boolean featurePresent = mService != null; 1370 if (!featurePresent && DEBUG) { 1371 Log.d(LOG_TAG, "Feature " + PackageManager.FEATURE_COMPANION_DEVICE_SETUP 1372 + " not available"); 1373 } 1374 return featurePresent; 1375 } 1376 1377 private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub { 1378 private final Handler mHandler; 1379 private final Callback mCallback; 1380 private final Executor mExecutor; 1381 AssociationRequestCallbackProxy( @onNull Executor executor, @NonNull Callback callback)1382 private AssociationRequestCallbackProxy( 1383 @NonNull Executor executor, @NonNull Callback callback) { 1384 mExecutor = executor; 1385 mHandler = null; 1386 mCallback = callback; 1387 } 1388 AssociationRequestCallbackProxy( @onNull Handler handler, @NonNull Callback callback)1389 private AssociationRequestCallbackProxy( 1390 @NonNull Handler handler, @NonNull Callback callback) { 1391 mHandler = handler; 1392 mExecutor = null; 1393 mCallback = callback; 1394 } 1395 1396 @Override onAssociationPending(@onNull PendingIntent pi)1397 public void onAssociationPending(@NonNull PendingIntent pi) { 1398 execute(mCallback::onAssociationPending, pi.getIntentSender()); 1399 } 1400 1401 @Override onAssociationCreated(@onNull AssociationInfo association)1402 public void onAssociationCreated(@NonNull AssociationInfo association) { 1403 execute(mCallback::onAssociationCreated, association); 1404 } 1405 1406 @Override onFailure(CharSequence error)1407 public void onFailure(CharSequence error) throws RemoteException { 1408 execute(mCallback::onFailure, error); 1409 } 1410 execute(Consumer<T> callback, T arg)1411 private <T> void execute(Consumer<T> callback, T arg) { 1412 if (mExecutor != null) { 1413 mExecutor.execute(() -> callback.accept(arg)); 1414 } else { 1415 mHandler.post(() -> callback.accept(arg)); 1416 } 1417 } 1418 } 1419 1420 private static class OnAssociationsChangedListenerProxy 1421 extends IOnAssociationsChangedListener.Stub { 1422 private final Executor mExecutor; 1423 private final OnAssociationsChangedListener mListener; 1424 OnAssociationsChangedListenerProxy(Executor executor, OnAssociationsChangedListener listener)1425 private OnAssociationsChangedListenerProxy(Executor executor, 1426 OnAssociationsChangedListener listener) { 1427 mExecutor = executor; 1428 mListener = listener; 1429 } 1430 1431 @Override onAssociationsChanged(@onNull List<AssociationInfo> associations)1432 public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) { 1433 mExecutor.execute(() -> mListener.onAssociationsChanged(associations)); 1434 } 1435 } 1436 1437 private static class OnTransportsChangedListenerProxy 1438 extends IOnTransportsChangedListener.Stub { 1439 private final Executor mExecutor; 1440 private final OnTransportsChangedListener mListener; 1441 OnTransportsChangedListenerProxy(Executor executor, OnTransportsChangedListener listener)1442 private OnTransportsChangedListenerProxy(Executor executor, 1443 OnTransportsChangedListener listener) { 1444 mExecutor = executor; 1445 mListener = listener; 1446 } 1447 1448 @Override onTransportsChanged(@onNull List<AssociationInfo> associations)1449 public void onTransportsChanged(@NonNull List<AssociationInfo> associations) { 1450 mExecutor.execute(() -> mListener.onTransportsChanged(associations)); 1451 } 1452 } 1453 1454 private static class OnMessageReceivedListenerProxy 1455 extends IOnMessageReceivedListener.Stub { 1456 private final Executor mExecutor; 1457 private final OnMessageReceivedListener mListener; 1458 OnMessageReceivedListenerProxy(Executor executor, OnMessageReceivedListener listener)1459 private OnMessageReceivedListenerProxy(Executor executor, 1460 OnMessageReceivedListener listener) { 1461 mExecutor = executor; 1462 mListener = listener; 1463 } 1464 1465 @Override onMessageReceived(int associationId, byte[] data)1466 public void onMessageReceived(int associationId, byte[] data) { 1467 mExecutor.execute(() -> mListener.onMessageReceived(associationId, data)); 1468 } 1469 } 1470 1471 private static class SystemDataTransferCallbackProxy extends ISystemDataTransferCallback.Stub { 1472 private final Executor mExecutor; 1473 private final OutcomeReceiver<Void, CompanionException> mCallback; 1474 SystemDataTransferCallbackProxy(Executor executor, OutcomeReceiver<Void, CompanionException> callback)1475 private SystemDataTransferCallbackProxy(Executor executor, 1476 OutcomeReceiver<Void, CompanionException> callback) { 1477 mExecutor = executor; 1478 mCallback = callback; 1479 } 1480 1481 @Override onResult()1482 public void onResult() { 1483 mExecutor.execute(() -> mCallback.onResult(null)); 1484 } 1485 1486 @Override onError(String error)1487 public void onError(String error) { 1488 mExecutor.execute(() -> mCallback.onError(new CompanionException(error))); 1489 } 1490 } 1491 1492 /** 1493 * Representation of an active system data transport. 1494 * <p> 1495 * Internally uses two threads to shuttle bidirectional data between a 1496 * remote device and a {@code socketpair} that the system is listening to. 1497 * This design ensures that data payloads are transported efficiently 1498 * without adding Binder traffic contention. 1499 */ 1500 private class Transport { 1501 private final int mAssociationId; 1502 private final InputStream mRemoteIn; 1503 private final OutputStream mRemoteOut; 1504 1505 private InputStream mLocalIn; 1506 private OutputStream mLocalOut; 1507 1508 private volatile boolean mStopped; 1509 Transport(int associationId, InputStream remoteIn, OutputStream remoteOut)1510 public Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) { 1511 mAssociationId = associationId; 1512 mRemoteIn = remoteIn; 1513 mRemoteOut = remoteOut; 1514 } 1515 start()1516 public void start() throws IOException { 1517 final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(); 1518 final ParcelFileDescriptor localFd = pair[0]; 1519 final ParcelFileDescriptor remoteFd = pair[1]; 1520 mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(localFd); 1521 mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(localFd); 1522 1523 try { 1524 mService.attachSystemDataTransport(mContext.getPackageName(), 1525 mContext.getUserId(), mAssociationId, remoteFd); 1526 } catch (RemoteException e) { 1527 throw new IOException("Failed to configure transport", e); 1528 } 1529 1530 new Thread(() -> { 1531 try { 1532 copyWithFlushing(mLocalIn, mRemoteOut); 1533 } catch (IOException e) { 1534 if (!mStopped) { 1535 Log.w(LOG_TAG, "Trouble during outgoing transport", e); 1536 stop(); 1537 } 1538 } 1539 }).start(); 1540 new Thread(() -> { 1541 try { 1542 copyWithFlushing(mRemoteIn, mLocalOut); 1543 } catch (IOException e) { 1544 if (!mStopped) { 1545 Log.w(LOG_TAG, "Trouble during incoming transport", e); 1546 stop(); 1547 } 1548 } 1549 }).start(); 1550 } 1551 stop()1552 public void stop() { 1553 mStopped = true; 1554 1555 try { 1556 mService.detachSystemDataTransport(mContext.getPackageName(), 1557 mContext.getUserId(), mAssociationId); 1558 } catch (RemoteException e) { 1559 Log.w(LOG_TAG, "Failed to detach transport", e); 1560 } 1561 1562 IoUtils.closeQuietly(mRemoteIn); 1563 IoUtils.closeQuietly(mRemoteOut); 1564 IoUtils.closeQuietly(mLocalIn); 1565 IoUtils.closeQuietly(mLocalOut); 1566 } 1567 1568 /** 1569 * Copy all data from the first stream to the second stream, flushing 1570 * after every write to ensure that we quickly deliver all pending data. 1571 */ copyWithFlushing(@onNull InputStream in, @NonNull OutputStream out)1572 private void copyWithFlushing(@NonNull InputStream in, @NonNull OutputStream out) 1573 throws IOException { 1574 byte[] buffer = new byte[8192]; 1575 int c; 1576 while ((c = in.read(buffer)) != -1) { 1577 out.write(buffer, 0, c); 1578 out.flush(); 1579 } 1580 } 1581 } 1582 } 1583