1 /*
2  * Copyright (C) 2022 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.car.occupantconnection;
18 
19 
20 import android.annotation.CallbackExecutor;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.car.Car;
26 import android.car.CarManagerBase;
27 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
28 import android.car.CarRemoteDeviceManager.AppState;
29 import android.car.CarRemoteDeviceManager.OccupantZoneState;
30 import android.os.Binder;
31 import android.os.IBinder;
32 import android.os.RemoteException;
33 import android.util.ArrayMap;
34 import android.util.Pair;
35 import android.util.Slog;
36 import android.util.SparseArray;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.util.Preconditions;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.Objects;
44 import java.util.concurrent.Executor;
45 
46 /**
47  * API for communication between different endpoints in the occupant zones in the car.
48  * <p>
49  * Unless specified explicitly, a client means an app that uses this API and runs as a
50  * foreground user in an occupant zone, while a peer client means an app that has the same package
51  * name as the caller app and runs as another foreground user (in another occupant zone or even
52  * another Android system).
53  * An endpoint means a component (such as a Fragment or an Activity) that has an instance of
54  * {@link CarOccupantConnectionManager}.
55  * <p>
56  * Communication between apps with different package names is not supported.
57  * <p>
58  * A common use case of this API is like:
59  * <pre>
60  *     ==========================================        =========================================
61  *     =        client1 (occupantZone1)         =        =        client2 (occupantZone2)        =
62  *     =                                        =        =                                       =
63  *     =    ************     ************       =        =    ************      ************     =
64  *     =    * sender1A *     * sender1B *       =        =    * sender2A *      * sender2B *     =
65  *     =    ************     ************       =        =    ************      ************     =
66  *     =                                        =        =                                       =
67  *     =    ****************************        =        =    ****************************       =
68  *     =    *     ReceiverService1     *        =        =    *     ReceiverService2     *       =
69  *     =    ****************************        =        =    ****************************       =
70  *     =                                        =        =                                       =
71  *     =    **************    **************    =        =    **************   **************    =
72  *     =    * receiver1A *    * receiver1B *    =        =    * receiver2A *   * receiver2B *    =
73  *     =    **************    **************    =        =    **************   **************    =
74  *     ==========================================        =========================================
75  *
76  *                 ****** Payload *****
77  *                 * ID: "receiver2A" *
78  *                 * value: "123"     *
79  *                 ********************                        Payload     |---> receiver2A
80  *     sender1A -------------------------->ReceiverService2--------------->|
81  *                                                                         |.... receiver2B
82  * </pre>
83  * <ul>
84  *   <li> Client1 and client2 must have the same package name. Client1 runs in occupantZone1
85  *        while client2 runs in occupantZone2. Sender1A (an endpoint in client1) wants to
86  *        send a {@link Payload} to receiver2A (an endpoint in client2).
87  *   <li> Pre-connection:
88  *     <ul>
89  *       <li> The client app inherits {@link AbstractReceiverService} and declares the service in
90  *            its manifest file.
91  *     </ul>
92  *   <li> Establish connection:
93  *     <ul>
94  *       <li> Sender1A monitors occupantZone2 by calling {@link
95  *            android.car.CarRemoteDeviceManager#registerStateCallback}.
96  *       <li> Sender1A waits until the {@link OccupantZoneState} of occupantZone2 becomes
97  *            {@link android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_CONNECTION_READY} and
98  *            the {@link AppState} of client2 becomes {@link
99  *            android.car.CarRemoteDeviceManager#FLAG_CLIENT_INSTALLED}, then requests a connection
100  *            to occupantZone2 by calling {@link #requestConnection}. If UI is needed to establish
101  *            the connection, sender1A must wait until {@link
102  *            android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED} and {@link
103  *            android.car.CarRemoteDeviceManager#FLAG_CLIENT_IN_FOREGROUND}).
104  *       <li> ReceiverService2 is started and bound by car service ({@link
105  *            com.android.car.occupantconnection.CarOccupantConnectionService} automatically.
106  *            ReceiverService2 is notified via {@link
107  *            AbstractReceiverService#onConnectionInitiated}.
108  *       <li> ReceiverService2 accepts the connection by calling {@link
109  *            AbstractReceiverService#acceptConnection}.
110  *       <li> Then the one-way connection is established. Sender1A is notified via {@link
111  *            ConnectionRequestCallback#onConnected}, and ReceiverService2 is notified via
112  *            {@link AbstractReceiverService#onConnected}.
113  *     </ul>
114  *   <li> Send Payload:
115  *     <ul>
116  *       <li> Sender1A sends a Payload to occupantZone2 by calling {@link #sendPayload}. To indicate
117  *            that the Payload is sent to receiver2A, Sender1A puts receiver2A's ID ("receiver2A")
118  *            into the Payload.
119  *       <li> ReceiverService2 is notified for the Payload via {@link
120  *            AbstractReceiverService#onPayloadReceived}.
121  *            In this method, ReceiverService2 can forward the Payload to client2's receiver
122  *            endpoints (if any), or cache the Payload and forward it later once a new receiver
123  *            endpoint is registered.
124  *     </ul>
125  *   <li> Register receiver:
126  *     <ul>
127  *       <li> Receiver2A calls {@link #registerReceiver} with ID "receiver2A". Then
128  *            ReceiverService2 is notified via {@link AbstractReceiverService#onReceiverRegistered}.
129  *            In that method, ReceiverService2 parses the Payload and finds that the Payload should
130  *            be sent to the endpoint with ID "receiver2A", then invokes {@link
131  *            AbstractReceiverService#forwardPayload} to forward the cached Payload to receiver2A.
132  *            <p>
133  *            Note: this step can be done before "Establish connection". In this case,
134  *            ReceiverService2 will be started and bound by car service early.
135  *            Once sender1A sends a Payload to occupantZone2, ReceiverService2 will be notified
136  *            via {@link AbstractReceiverService#onReceiverRegistered}. In that method,
137  *            ReceiverService2 can forward the Payload to Receiver2A without caching.
138  *       <li> Receiver2A is notified for the Payload via {@link PayloadCallback#onPayloadReceived}.
139  *     </ul>
140  *   <li> Terminate the connection:
141  *   <ul>
142  *     <li> Sender1A terminates the connection to occupantZone2:
143  *          Once sender1A no longer needs to send Payload to occupantZone2, it terminates the
144  *          connection by calling {@link #disconnect}. Then sender1A is notified via
145  *          {@link ConnectionRequestCallback#onDisconnected}, and ReceiverService2 is notified via
146  *          {@link AbstractReceiverService#onDisconnected}.
147  *     <li> Unregister receiver2A:
148  *          Once receiver2A no longer needs to receive Payload from any other occupant zones,
149  *          it calls {@link #unregisterReceiver}.
150  *    <li> Unbound and destroy ReceiverService2:
151  *         Since all the senders have disconnected from occupantZone2 and there is no receiver
152  *         registered in occupantZone2, ReceiverService2 will be unbound and destroyed
153  *         automatically.
154  *   </ul>
155  *   <li> Sender1A stops monitoring other occupant zones by calling {@link
156  *        android.car.CarRemoteDeviceManager#unregisterStateCallback}. This step can
157  *        be done before or after "Terminate the connection".
158  * </ul>
159  * <p>
160  * For a given {@link android.car.Car} instance, the CarOccupantConnectionManager is a singleton.
161  * However, the client app may create multiple {@link android.car.Car} instances thus create
162  * multiple CarOccupantConnectionManager instances. These CarOccupantConnectionManager instances
163  * are treated as the same instance for the client app. For example:
164  * <ul>
165  *   <li> Sender1A creates a CarOccupantConnectionManager instance (managerA), while sender1B
166  *        creates a different CarOccupantConnectionManager instance (managerB). Then sender1A uses
167  *        managerA to request a connection to occupantZone2. Once connected, sender1B can use
168  *        managerB to send Payload to occupantZone2 without requesting a new connection.
169  *        To know whether it is connected to occupantZone2, sender1B can call {@link #isConnected}.
170  *   <li> Besides, sender1B can terminate the connection by calling managerB#disconnect(), despite
171  *        that the connection was requested by sender1A. Once the connection is terminated, sender1A
172  *        will be notified via {@link ConnectionRequestCallback#onDisconnected}, and sender1B will
173  *        not be notified since it didn't register register the {@link ConnectionRequestCallback}.
174  * </ul>
175  *
176  * @hide
177  */
178 @SystemApi
179 public final class CarOccupantConnectionManager extends CarManagerBase {
180 
181     private static final String TAG = CarOccupantConnectionManager.class.getSimpleName();
182 
183     /** The connection request has no error. */
184     public static final int CONNECTION_ERROR_NONE = 0;
185 
186     /** The connection request failed because of an error of unidentified cause. */
187     public static final int CONNECTION_ERROR_UNKNOWN = 1;
188 
189     /**
190      * The connection request failed because the peer occupant zone was not ready for connection.
191      * To avoid this error, the caller endpoint should ensure that the state of the peer occupant
192      * zone is {@link android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_CONNECTION_READY} before
193      * requesting a connection to it.
194      */
195     public static final int CONNECTION_ERROR_NOT_READY = 2;
196 
197     /**
198      * The connection request failed because the peer app was not installed. To avoid this error,
199      * the caller endpoint should ensure that the state of the peer app is {@link
200      * android.car.CarRemoteDeviceManager#FLAG_CLIENT_INSTALLED} before requesting a connection to
201      * it.
202      */
203     public static final int CONNECTION_ERROR_PEER_APP_NOT_INSTALLED = 3;
204 
205     /**
206      * The connection request failed because its long version code ({@link
207      * PackageInfo#getLongVersionCode}) didn't match the peer app's long version code.
208      */
209     public static final int CONNECTION_ERROR_LONG_VERSION_NOT_MATCH = 4;
210 
211     /**
212      * The connection request failed because its signing info ({@link PackageInfo#signingInfo}
213      * didn't match the peer app's signing info.
214      */
215     public static final int CONNECTION_ERROR_SIGNATURE_NOT_MATCH = 5;
216 
217     /** The connection request failed because the user rejected it. */
218     public static final int CONNECTION_ERROR_USER_REJECTED = 6;
219 
220     /**
221      * The maximum value of predefined connection error code. If the client app wants to pass a
222      * custom value in {@link AbstractReceiverService#rejectConnection}, the custom value must be
223      * larger than this value, otherwise the sender client might get the wrong connection error code
224      * when its connection request fails.
225      */
226     public static final int CONNECTION_ERROR_PREDEFINED_MAXIMUM_VALUE = 10000;
227 
228     /**
229      * Flags for the error type of connection request.
230      *
231      * @hide
232      */
233     @IntDef(flag = false, prefix = {"CONNECTION_ERROR_"}, value = {
234             CONNECTION_ERROR_NONE,
235             CONNECTION_ERROR_UNKNOWN,
236             CONNECTION_ERROR_NOT_READY,
237             CONNECTION_ERROR_PEER_APP_NOT_INSTALLED,
238             CONNECTION_ERROR_LONG_VERSION_NOT_MATCH,
239             CONNECTION_ERROR_SIGNATURE_NOT_MATCH,
240             CONNECTION_ERROR_USER_REJECTED,
241             CONNECTION_ERROR_PREDEFINED_MAXIMUM_VALUE
242     })
243     @Retention(RetentionPolicy.SOURCE)
244     public @interface ConnectionError {
245     }
246 
247     /**
248      * A callback for lifecycle events of a connection request. When the endpoint (sender) calls
249      * {@link #requestConnection} to connect to its peer client, it will be notified for the events.
250      * The sender may call {@link #cancelConnection} if none of the events are triggered for a
251      * long time.
252      */
253     public interface ConnectionRequestCallback {
254         /**
255          * Invoked when the one-way connection has been established.
256          * <p>
257          * In order to establish the connection, the receiver {@link AbstractReceiverService}
258          * must accept the connection, and the sender must not cancel the request before the
259          * connection is established.
260          * Once the connection is established, the sender can send {@link Payload} to the
261          * receiver client.
262          */
onConnected(@onNull OccupantZoneInfo receiverZone)263         void onConnected(@NonNull OccupantZoneInfo receiverZone);
264 
265         /**
266          * Invoked when there was an error when establishing the connection. For example, the
267          * receiver client is not ready for connection, or the receiver client rejected the
268          * connection request.
269          *
270          * @param connectionError could be any value of {@link ConnectionError}, or an app-defined
271          *                        value
272          */
onFailed(@onNull OccupantZoneInfo receiverZone, int connectionError)273         void onFailed(@NonNull OccupantZoneInfo receiverZone, int connectionError);
274 
275         /**
276          * Invoked when the connection is terminated. For example, the receiver {@link
277          * AbstractReceiverService} is unbound and destroyed, is crashed, or the receiver client
278          * has become unreachable.
279          * <p>
280          * Once disconnected, the sender can no longer send {@link Payload} to the receiver
281          * client.
282          */
onDisconnected(@onNull OccupantZoneInfo receiverZone)283         void onDisconnected(@NonNull OccupantZoneInfo receiverZone);
284     }
285 
286     /** A callback to receive a {@link Payload}. */
287     public interface PayloadCallback {
288         /**
289          * Invoked when the receiver endpoint has received a {@link Payload} from {@code
290          * senderZone}.
291          */
onPayloadReceived(@onNull OccupantZoneInfo senderZone, @NonNull Payload payload)292         void onPayloadReceived(@NonNull OccupantZoneInfo senderZone,
293                 @NonNull Payload payload);
294     }
295 
296     /** An exception to indicate that it failed to send the {@link Payload}. */
297     public static final class PayloadTransferException extends Exception {
298     }
299 
300     private final ICarOccupantConnection mService;
301 
302     private final Object mLock = new Object();
303 
304     private final String mPackageName;
305 
306     /**
307      * A map of connection requests. The key is the zone ID of the receiver occupant zone, and
308      * the value is the callback and associated executor.
309      */
310     @GuardedBy("mLock")
311     private final SparseArray<Pair<ConnectionRequestCallback, Executor>>
312             mConnectionRequestMap = new SparseArray<>();
313 
314     private final IConnectionRequestCallback mBinderConnectionRequestCallback =
315             new IConnectionRequestCallback.Stub() {
316                 @Override
317                 public void onConnected(OccupantZoneInfo receiverZone) {
318                     synchronized (mLock) {
319                         Pair<ConnectionRequestCallback, Executor> pair =
320                                 mConnectionRequestMap.get(receiverZone.zoneId);
321                         if (pair == null) {
322                             Slog.e(TAG, "onConnected: no pending connection request");
323                             return;
324                         }
325                         // Notify the sender of success.
326                         ConnectionRequestCallback callback = pair.first;
327                         Executor executor = pair.second;
328                         long token = Binder.clearCallingIdentity();
329                         try {
330                             executor.execute(() -> callback.onConnected(receiverZone));
331                         } finally {
332                             Binder.restoreCallingIdentity(token);
333                         }
334 
335                         // Unlike other onFoo() methods, we shouldn't remove the callback here
336                         // because we need to invoke it once it is disconnected.
337                     }
338                 }
339 
340                 @Override
341                 public void onFailed(OccupantZoneInfo receiverZone, int connectionError) {
342                     synchronized (mLock) {
343                         Pair<ConnectionRequestCallback, Executor> pair =
344                                 mConnectionRequestMap.get(receiverZone.zoneId);
345                         if (pair == null) {
346                             Slog.e(TAG, "onFailed: no pending connection request");
347                             return;
348                         }
349                         // Notify the sender of failure.
350                         ConnectionRequestCallback callback = pair.first;
351                         Executor executor = pair.second;
352                         long token = Binder.clearCallingIdentity();
353                         try {
354                             executor.execute(
355                                     () -> callback.onFailed(receiverZone, connectionError));
356                         } finally {
357                             Binder.restoreCallingIdentity(token);
358                         }
359 
360                         mConnectionRequestMap.remove(receiverZone.zoneId);
361                     }
362                 }
363 
364                 @Override
365                 public void onDisconnected(OccupantZoneInfo receiverZone) {
366                     synchronized (mLock) {
367                         Pair<ConnectionRequestCallback, Executor> pair =
368                                 mConnectionRequestMap.get(receiverZone.zoneId);
369                         if (pair == null) {
370                             Slog.e(TAG, "onDisconnected: no pending connection request");
371                             return;
372                         }
373                         // Notify the sender of disconnection.
374                         ConnectionRequestCallback callback = pair.first;
375                         Executor executor = pair.second;
376                         long token = Binder.clearCallingIdentity();
377                         try {
378                             executor.execute(() -> callback.onDisconnected(receiverZone));
379                         } finally {
380                             Binder.restoreCallingIdentity(token);
381                         }
382 
383                         mConnectionRequestMap.remove(receiverZone.zoneId);
384                     }
385                 }
386             };
387 
388     /**
389      * A map of registered receivers. The key is the endpointId of the receiver, the value is
390      * the associated callback and the Executor of the callback.
391      */
392     @GuardedBy("mLock")
393     private final ArrayMap<String, Pair<PayloadCallback, Executor>> mReceiverPayloadCallbackMap =
394             new ArrayMap<>();
395 
396     private final IPayloadCallback mBinderPayloadCallback = new IPayloadCallback.Stub() {
397         @Override
398         public void onPayloadReceived(OccupantZoneInfo senderZone, String receiverEndpointId,
399                 Payload payload) {
400             Pair<PayloadCallback, Executor> pair;
401             synchronized (mLock) {
402                 pair = mReceiverPayloadCallbackMap.get(receiverEndpointId);
403                 if (pair == null) {
404                     // This should never happen, but let's be cautious.
405                     Slog.e(TAG, "Couldn't find receiver " + receiverEndpointId);
406                     return;
407                 }
408             }
409             PayloadCallback callback = pair.first;
410             Executor executor = pair.second;
411             long token = Binder.clearCallingIdentity();
412             try {
413                 executor.execute(() -> callback.onPayloadReceived(senderZone, payload));
414             } finally {
415                 Binder.restoreCallingIdentity(token);
416             }
417         }
418     };
419 
420     /** @hide */
CarOccupantConnectionManager(Car car, IBinder service)421     public CarOccupantConnectionManager(Car car, IBinder service) {
422         super(car);
423         mService = ICarOccupantConnection.Stub.asInterface(service);
424         mPackageName = mCar.getContext().getPackageName();
425     }
426 
427     /** @hide */
428     @Override
onCarDisconnected()429     public void onCarDisconnected() {
430         synchronized (mLock) {
431             mConnectionRequestMap.clear();
432             mReceiverPayloadCallbackMap.clear();
433         }
434     }
435 
436     /**
437      * Registers a {@link PayloadCallback} to receive {@link Payload}. If the {@link
438      * AbstractReceiverService} in the caller app was not started yet, it will be started and
439      * bound by car service automatically.
440      * <p>
441      * The caller endpoint must call {@link #unregisterReceiver} before it is destroyed.
442      *
443      * @param receiverEndpointId the ID of this receiver endpoint. Since there might be multiple
444      *                           receiver endpoints in the client app, the ID can be used by the
445      *                           client app ({@link AbstractReceiverService}) to decide which
446      *                           endpoint(s) to dispatch the Payload to. The client app can use any
447      *                           String as the ID, as long as it is unique among the client app.
448      * @param executor           the Executor to run the callback
449      * @param callback           the callback notified when this endpoint receives a Payload
450      * @throws IllegalStateException if the {@code receiverEndpointId} had a {@link PayloadCallback}
451      *                               registered
452      */
453     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
registerReceiver(@onNull String receiverEndpointId, @NonNull @CallbackExecutor Executor executor, @NonNull PayloadCallback callback)454     public void registerReceiver(@NonNull String receiverEndpointId,
455             @NonNull @CallbackExecutor Executor executor,
456             @NonNull PayloadCallback callback) {
457         Objects.requireNonNull(receiverEndpointId, "receiverEndpointId cannot be null");
458         Objects.requireNonNull(executor, "executor cannot be null");
459         Objects.requireNonNull(callback, "callback cannot be null");
460         synchronized (mLock) {
461             try {
462                 mService.registerReceiver(mPackageName, receiverEndpointId, mBinderPayloadCallback);
463                 // Save the callback only after the remote call succeeded.
464                 mReceiverPayloadCallbackMap.put(receiverEndpointId, new Pair<>(callback, executor));
465             } catch (RemoteException e) {
466                 Slog.e(TAG, "Failed to register receiver: " + receiverEndpointId);
467                 handleRemoteExceptionFromCarService(e);
468             }
469         }
470     }
471 
472     /**
473      * Unregisters the existing {@link PayloadCallback} for {@code receiverEndpointId}.
474      * <p>
475      * This method can be called after calling {@link #registerReceiver} once the receiver
476      * endpoint no longer needs to receive Payload, or becomes inactive.
477      * This method must be called before the receiver endpoint is destroyed. Failing to call this
478      * method might cause the AbstractReceiverService to persist.
479      *
480      * @throws IllegalStateException if the {@code receiverEndpointId} had no {@link
481      *                               PayloadCallback} registered
482      */
483     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
unregisterReceiver(@onNull String receiverEndpointId)484     public void unregisterReceiver(@NonNull String receiverEndpointId) {
485         Objects.requireNonNull(receiverEndpointId, "receiverEndpointId cannot be null");
486         synchronized (mLock) {
487             try {
488                 mService.unregisterReceiver(mPackageName, receiverEndpointId);
489                 // Remove the callback after the remote call succeeded.
490                 mReceiverPayloadCallbackMap.remove(receiverEndpointId);
491             } catch (RemoteException e) {
492                 Slog.e(TAG, "Failed to unregister receiver: " + receiverEndpointId);
493                 handleRemoteExceptionFromCarService(e);
494             }
495         }
496     }
497 
498     /**
499      * Sends a request to connect to the receiver client in {@code receiverZone}. The {@link
500      * AbstractReceiverService} in the receiver client will be started and bound automatically if it
501      * was not started yet.
502      * <p>
503      * This method should only be called when the state of the {@code receiverZone} contains
504      * {@link android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_CONNECTION_READY} (and
505      * {@link android.car.CarRemoteDeviceManager#FLAG_OCCUPANT_ZONE_SCREEN_UNLOCKED} and {@link
506      * android.car.CarRemoteDeviceManager#FLAG_CLIENT_IN_FOREGROUND} if UI is needed to
507      * establish the connection). Otherwise, errors may occur.
508      * <p>
509      * For security, it is highly recommended that the sender not request a connection to the
510      * receiver client if the state of the receiver client doesn't contain
511      * {@link android.car.CarRemoteDeviceManager#FLAG_CLIENT_SAME_LONG_VERSION} or
512      * {@link android.car.CarRemoteDeviceManager#FLAG_CLIENT_SAME_SIGNATURE}. If the sender still
513      * wants to request the connection in the case above, it should call
514      * {@link android.car.CarRemoteDeviceManager#getEndpointPackageInfo} to get the receiver's
515      * {@link android.content.pm.PackageInfo} and check if it's valid before requesting the
516      * connection.
517      * <p>
518      * The caller may call {@link #cancelConnection} to cancel the request.
519      * <p>
520      * The connection is one-way. In other words, the receiver can't send {@link Payload} to the
521      * sender. If the receiver wants to send {@link Payload}, it must call this method to become
522      * a sender.
523      * <p>
524      * The caller must not request another connection to the same {@code receiverZone} if there
525      * is an established connection or pending connection (a connection request that has not been
526      * responded yet) to {@code receiverZone}.
527      * The caller must call {@link #disconnect} before it is destroyed.
528      *
529      * @param receiverZone the occupant zone to connect to
530      * @param executor     the Executor to run the callback
531      * @param callback     the callback notified for the request result
532      * @throws IllegalStateException if there is an established connection or pending connection to
533      *                               {@code receiverZone}
534      */
535     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
requestConnection(@onNull OccupantZoneInfo receiverZone, @NonNull @CallbackExecutor Executor executor, @NonNull ConnectionRequestCallback callback)536     public void requestConnection(@NonNull OccupantZoneInfo receiverZone,
537             @NonNull @CallbackExecutor Executor executor,
538             @NonNull ConnectionRequestCallback callback) {
539         Objects.requireNonNull(receiverZone, "receiverZone cannot be null");
540         Objects.requireNonNull(executor, "executor cannot be null");
541         Objects.requireNonNull(callback, "callback cannot be null");
542         synchronized (mLock) {
543             Preconditions.checkState(!mConnectionRequestMap.contains(receiverZone.zoneId),
544                     "Already requested a connection to " + receiverZone);
545             try {
546                 mService.requestConnection(mPackageName, receiverZone,
547                         mBinderConnectionRequestCallback);
548                 mConnectionRequestMap.put(receiverZone.zoneId, new Pair<>(callback, executor));
549             } catch (RemoteException e) {
550                 Slog.e(TAG, "Failed to request connection");
551                 handleRemoteExceptionFromCarService(e);
552             }
553         }
554     }
555 
556     /**
557      * Cancels the pending connection request to the peer client in {@code receiverZone}.
558      * <p>
559      * The caller endpoint may call this method when it has requested a connection, but hasn't
560      * received any response for a long time, or the user wants to cancel the request explicitly.
561      * In other words, this method should be called after {@link #requestConnection}, and before
562      * any events in the {@link ConnectionRequestCallback} is triggered.
563      *
564      * @throws IllegalStateException if this {@link CarOccupantConnectionManager} has no pending
565      *                               connection request to {@code receiverZone}
566      */
567     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
cancelConnection(@onNull OccupantZoneInfo receiverZone)568     public void cancelConnection(@NonNull OccupantZoneInfo receiverZone) {
569         Objects.requireNonNull(receiverZone, "receiverZone cannot be null");
570         synchronized (mLock) {
571             Preconditions.checkState(mConnectionRequestMap.contains(receiverZone.zoneId),
572                     "This manager instance has no connection request to " + receiverZone);
573             try {
574                 mService.cancelConnection(mPackageName, receiverZone);
575                 mConnectionRequestMap.remove(receiverZone.zoneId);
576             } catch (RemoteException e) {
577                 Slog.e(TAG, "Failed to cancel connection");
578                 handleRemoteExceptionFromCarService(e);
579             }
580         }
581     }
582 
583     /**
584      * Sends the {@code payload} to the peer client in {@code receiverZone}.
585      * <p>
586      * Different sender endpoints in the same client app are treated as the same sender. If the
587      * sender endpoints need to differentiate themselves, they can put the identity info into the
588      * payload.
589      *
590      * @throws IllegalStateException    if it was not connected to the peer client in
591      *                                  {@code receiverZone}
592      * @throws PayloadTransferException if the payload was not sent. For example, this method is
593      *                                  called when the connection is not established or has been
594      *                                  terminated, or an internal error occurred.
595      */
596     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
sendPayload(@onNull OccupantZoneInfo receiverZone, @NonNull Payload payload)597     public void sendPayload(@NonNull OccupantZoneInfo receiverZone, @NonNull Payload payload)
598             throws PayloadTransferException {
599         Objects.requireNonNull(receiverZone, "receiverZone cannot be null");
600         Objects.requireNonNull(payload, "payload cannot be null");
601         try {
602             mService.sendPayload(mPackageName, receiverZone, payload);
603         } catch (IllegalStateException e) {
604             Slog.e(TAG, "Failed to send Payload to " + receiverZone);
605             throw new PayloadTransferException();
606         } catch (RemoteException e) {
607             Slog.e(TAG, "Failed to send Payload to " + receiverZone);
608             handleRemoteExceptionFromCarService(e);
609         }
610     }
611 
612     /**
613      * Disconnects from the peer client in {@code receiverZone}.
614      * <p>
615      * This method can be called as soon as the caller app no longer needs to send {@link Payload}
616      * to {@code receiverZone}. If there are multiple sender endpoints in the client app reuse the
617      * same connection, this method should be called when all sender endpoints no longer need to
618      * send Payload to {@code receiverZone}.
619      * <p>
620      * This method must be called before the caller is destroyed. Failing to call this method might
621      * cause the {@link AbstractReceiverService} in the peer client to persist.
622      *
623      * @throws IllegalStateException if it was not connected to the peer client in
624      *                               {@code receiverZone}
625      */
626     @SuppressWarnings("[NotCloseable]")
627     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
disconnect(@onNull OccupantZoneInfo receiverZone)628     public void disconnect(@NonNull OccupantZoneInfo receiverZone) {
629         Objects.requireNonNull(receiverZone, "receiverZone cannot be null");
630         try {
631             mService.disconnect(mPackageName, receiverZone);
632         } catch (RemoteException e) {
633             Slog.e(TAG, "Failed to disconnect");
634             handleRemoteExceptionFromCarService(e);
635         }
636     }
637 
638     /**
639      * Returns whether it is connected to its peer client in {@code receiverZone}. When it is
640      * connected, it can send {@link Payload} to the peer client.
641      * <p>
642      * Note: the connection is one-way. The peer client can not send {@link Payload} to this client
643      * unless the peer client is also connected to this client.
644      */
645     @SuppressWarnings("[NotCloseable]")
646     @RequiresPermission(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)
isConnected(@onNull OccupantZoneInfo receiverZone)647     public boolean isConnected(@NonNull OccupantZoneInfo receiverZone) {
648         Objects.requireNonNull(receiverZone, "receiverZone cannot be null");
649         try {
650             return mService.isConnected(mPackageName, receiverZone);
651         } catch (RemoteException e) {
652             Slog.e(TAG, "Failed to get connection state");
653             return handleRemoteExceptionFromCarService(e, false);
654         }
655     }
656 }
657