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