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