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