• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &lt; 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