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