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