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 import static android.car.Car.CAR_INTENT_ACTION_RECEIVER_SERVICE; 20 import static android.car.occupantconnection.CarOccupantConnectionManager.CONNECTION_ERROR_LONG_VERSION_NOT_MATCH; 21 import static android.car.occupantconnection.CarOccupantConnectionManager.CONNECTION_ERROR_SIGNATURE_NOT_MATCH; 22 import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES; 23 24 import static com.android.car.internal.util.VersionUtils.assertPlatformVersionAtLeastU; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.SuppressLint; 29 import android.annotation.SystemApi; 30 import android.app.Service; 31 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 32 import android.car.annotation.ApiRequirements; 33 import android.car.builtin.util.Slogf; 34 import android.content.Intent; 35 import android.content.pm.PackageInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.SigningInfo; 38 import android.os.IBinder; 39 import android.os.RemoteException; 40 41 import com.android.car.internal.util.BinderKeyValueContainer; 42 43 import java.io.FileDescriptor; 44 import java.io.PrintWriter; 45 import java.util.Set; 46 47 /** 48 * A service used to respond to connection requests from peer clients on other occupant zones, 49 * receive {@link Payload} from peer clients, cache the received Payload, and dispatch it to the 50 * receiver endpoints in this client. 51 * <p> 52 * The client app must extend this service to receive Payload from peer clients. When declaring 53 * this service in the manifest file, the client must add an intent filter with action 54 * {@value android.car.Car#CAR_INTENT_ACTION_RECEIVER_SERVICE} for this service, and require 55 * {@code android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE}. For example: 56 * <pre>{@code 57 * <service android:name=".MyReceiverService" 58 * android:permission="android.car.occupantconnection.permission.BIND_RECEIVER_SERVICE" 59 * android:exported="true"> 60 * <intent-filter> 61 * <action android:name="android.car.intent.action.RECEIVER_SERVICE" /> 62 * </intent-filter> 63 * </service>} 64 * </pre> 65 * <p> 66 * This service runs on the main thread of the client app, and is a singleton for the client app. 67 * The lifecycle of this service is managed by car service ({@link 68 * com.android.car.occupantconnection.CarOccupantConnectionService}). 69 * <p> 70 * This service can be bound by car service in two ways: 71 * <ul> 72 * <li> A sender endpoint in the peer client calls {@link 73 * CarOccupantConnectionManager#requestConnection} to connect to this client. 74 * <li> A receiver endpoint in this client calls {@link 75 * CarOccupantConnectionManager#registerReceiver}. 76 * </ul> 77 * <p> 78 * Once all the senders have disconnected from this client and there is no receiver endpoints 79 * registered in this client, this service will be unbound by car service automatically. 80 * <p> 81 * When this service is crashed, all connections to this client will be terminated. As a result, 82 * all senders that were connected to this client will be notified via {@link 83 * CarOccupantConnectionManager.ConnectionRequestCallback#onDisconnected}. In addition, the cached 84 * Payload will be lost, if any. The senders are responsible for resending the Payload if needed. 85 * 86 * @hide 87 */ 88 @SystemApi 89 public abstract class AbstractReceiverService extends Service { 90 91 private static final String TAG = AbstractReceiverService.class.getSimpleName(); 92 private static final String INDENTATION_2 = " "; 93 private static final String INDENTATION_4 = " "; 94 95 /** 96 * A map of receiver endpoints in this client. The key is the ID of the endpoint, the value is 97 * the associated payload callback. 98 * <p> 99 * Although it is unusual, the process that registered the payload callback (process1) might be 100 * different from the process that this service is running (process2). When process1 is dead, 101 * if this service invokes the dead callback, a DeadObjectException will be thrown. 102 * To avoid that, the callbacks are stored in this BinderKeyValueContainer so that dead 103 * callbacks can be removed automatically. 104 */ 105 private final BinderKeyValueContainer<String, IPayloadCallback> mReceiverEndpointMap = 106 new BinderKeyValueContainer<>(); 107 108 private IBackendConnectionResponder mBackendConnectionResponder; 109 private long mMyVersionCode; 110 111 private final IBackendReceiver.Stub mBackendReceiver = new IBackendReceiver.Stub() { 112 @Override 113 public void registerReceiver(String receiverEndpointId, IPayloadCallback callback) { 114 mReceiverEndpointMap.put(receiverEndpointId, callback); 115 AbstractReceiverService.this.onReceiverRegistered(receiverEndpointId); 116 } 117 118 @Override 119 public void unregisterReceiver(String receiverEndpointId) { 120 mReceiverEndpointMap.remove(receiverEndpointId); 121 } 122 123 @Override 124 public void registerBackendConnectionResponder(IBackendConnectionResponder responder) { 125 mBackendConnectionResponder = responder; 126 } 127 128 @Override 129 public void onPayloadReceived(OccupantZoneInfo senderZone, Payload payload) { 130 AbstractReceiverService.this.onPayloadReceived(senderZone, payload); 131 } 132 133 @Override 134 public void onConnectionInitiated(OccupantZoneInfo senderZone, long senderVersion, 135 SigningInfo senderSigningInfo) { 136 if (!isSenderCompatible(senderVersion)) { 137 Slogf.w(TAG, "Reject the connection request from %s because its long version" 138 + " code %d doesn't match the receiver's %d ", senderZone, 139 senderVersion, mMyVersionCode); 140 AbstractReceiverService.this.rejectConnection(senderZone, 141 CONNECTION_ERROR_LONG_VERSION_NOT_MATCH); 142 return; 143 } 144 if (!isSenderAuthorized(senderSigningInfo)) { 145 Slogf.w(TAG, "Reject the connection request from %s because its SigningInfo" 146 + " doesn't match", senderZone); 147 AbstractReceiverService.this.rejectConnection(senderZone, 148 CONNECTION_ERROR_SIGNATURE_NOT_MATCH); 149 return; 150 } 151 AbstractReceiverService.this.onConnectionInitiated(senderZone); 152 } 153 154 @Override 155 public void onConnected(OccupantZoneInfo senderZone) { 156 AbstractReceiverService.this.onConnected(senderZone); 157 } 158 159 @Override 160 public void onConnectionCanceled(OccupantZoneInfo senderZone) { 161 AbstractReceiverService.this.onConnectionCanceled(senderZone); 162 } 163 164 @Override 165 public void onDisconnected(OccupantZoneInfo senderZone) { 166 AbstractReceiverService.this.onDisconnected(senderZone); 167 } 168 }; 169 170 /** 171 * {@inheritDoc} 172 */ 173 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 174 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 175 @Override onCreate()176 public void onCreate() { 177 super.onCreate(); 178 assertPlatformVersionAtLeastU(); 179 try { 180 PackageInfo myInfo = getPackageManager().getPackageInfo(getPackageName(), 181 GET_SIGNING_CERTIFICATES); 182 mMyVersionCode = myInfo.getLongVersionCode(); 183 } catch (PackageManager.NameNotFoundException e) { 184 throw new RuntimeException("Couldn't find the PackageInfo of " + getPackageName(), e); 185 } 186 } 187 188 /** 189 * {@inheritDoc} 190 * <p> 191 * To prevent the client app overriding this method improperly, this method is {@code final}. 192 * If the client app needs to bind to this service, it should override {@link 193 * #onLocalServiceBind}. 194 */ 195 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 196 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 197 @Nullable 198 @Override onBind(@onNull Intent intent)199 public final IBinder onBind(@NonNull Intent intent) { 200 assertPlatformVersionAtLeastU(); 201 if (CAR_INTENT_ACTION_RECEIVER_SERVICE.equals(intent.getAction())) { 202 return mBackendReceiver.asBinder(); 203 } 204 return onLocalServiceBind(intent); 205 } 206 207 /** 208 * Returns the communication channel to this service. If the client app needs to bind to this 209 * service and get a communication channel to this service, it should override this method 210 * instead of {@link #onBind}. 211 */ 212 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 213 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 214 @Nullable onLocalServiceBind(@onNull Intent intent)215 public IBinder onLocalServiceBind(@NonNull Intent intent) { 216 assertPlatformVersionAtLeastU(); 217 return null; 218 } 219 220 /** 221 * Invoked when this service has received {@code payload} from its peer client on 222 * {@code senderZone}. 223 * <p> 224 * The inheritance of this service should override this method to 225 * <ul> 226 * <li> forward the {@code payload} to the corresponding receiver endpoint(s), if any, and/or 227 * <li> cache the {@code payload}, then dispatch it when a new receiver endpoint is 228 * registered. The inheritance should clear the cache once it is no longer needed. 229 * </ul> 230 */ 231 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 232 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onPayloadReceived(@onNull OccupantZoneInfo senderZone, @NonNull Payload payload)233 public abstract void onPayloadReceived(@NonNull OccupantZoneInfo senderZone, 234 @NonNull Payload payload); 235 236 /** 237 * Invoked when a receiver endpoint is registered. 238 * <p> 239 * The inheritance of this service can override this method to forward the cached Payload 240 * (if any) to the newly registered endpoint. The inheritance of this service doesn't need to 241 * override this method if it never caches the Payload. 242 * 243 * @param receiverEndpointId the ID of the newly registered endpoint 244 */ 245 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 246 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onReceiverRegistered(@onNull String receiverEndpointId)247 public void onReceiverRegistered(@NonNull String receiverEndpointId) { 248 assertPlatformVersionAtLeastU(); 249 } 250 251 /** 252 * Returns whether the long version code ({@link PackageInfo#getLongVersionCode}) of the sender 253 * app is compatible with the receiver app's. If it doesn't match, this service will reject the 254 * connection request from the sender. 255 * <p> 256 * The default implementation checks whether the version codes are identical. This is fine if 257 * all the peer clients run on the same Android instance, since PackageManager doesn't allow to 258 * install two different apps with the same package name - even for different users. 259 * However, if the peer clients run on different Android instances, and the app wants to support 260 * connection between them even if they have different versions, the app will need to override 261 * this method. 262 */ 263 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 264 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 265 @SuppressLint("OnNameExpected") isSenderCompatible(long senderVersion)266 public boolean isSenderCompatible(long senderVersion) { 267 assertPlatformVersionAtLeastU(); 268 return mMyVersionCode == senderVersion; 269 } 270 271 /** 272 * Returns whether the signing info ({@link PackageInfo#signingInfo} of the sender app is 273 * authorized. If it is not authorized, this service will reject the connection request from 274 * the sender. 275 * <p> 276 * The default implementation simply returns {@code true}. This is fine if all the peer clients 277 * run on the same Android instance, since PackageManager doesn't allow to install two different 278 * apps with the same package name - even for different users. 279 * However, if the peer clients run on different Android instances, the app must override this 280 * method for security. 281 */ 282 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 283 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 284 @SuppressLint("OnNameExpected") isSenderAuthorized(@onNull SigningInfo senderSigningInfo)285 public boolean isSenderAuthorized(@NonNull SigningInfo senderSigningInfo) { 286 assertPlatformVersionAtLeastU(); 287 return true; 288 } 289 290 /** 291 * Invoked when the sender client in {@code senderZone} has requested a connection to this 292 * client. 293 * <p> 294 * If user confirmation is needed to establish the connection, the inheritance can override 295 * this method to launch a permission activity, and call {@link #acceptConnection} or 296 * {@link #rejectConnection} based on the result. For driving safety, the permission activity 297 * must be distraction optimized. Alternatively, the permission can be granted during device 298 * setup. 299 */ 300 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 301 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onConnectionInitiated(@onNull OccupantZoneInfo senderZone)302 public abstract void onConnectionInitiated(@NonNull OccupantZoneInfo senderZone); 303 304 /** 305 * Invoked when the one-way connection has been established. 306 * <p> 307 * In order to establish the connection, the inheritance of this service must call 308 * {@link #acceptConnection}, and the sender must NOT call {@link 309 * CarOccupantConnectionManager#cancelConnection} before the connection is established. 310 * <p> 311 * Once the connection is established, the sender can send {@link Payload} to this client. 312 */ 313 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 314 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onConnected(@onNull OccupantZoneInfo senderZone)315 public void onConnected(@NonNull OccupantZoneInfo senderZone) { 316 assertPlatformVersionAtLeastU(); 317 } 318 319 /** 320 * Invoked when the sender has canceled the pending connection request, or has become 321 * unreachable after sending the connection request. 322 */ 323 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 324 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onConnectionCanceled(@onNull OccupantZoneInfo senderZone)325 public void onConnectionCanceled(@NonNull OccupantZoneInfo senderZone) { 326 assertPlatformVersionAtLeastU(); 327 } 328 329 /** 330 * Invoked when the connection is terminated. For example, the sender on {@code senderZone} 331 * has called {@link CarOccupantConnectionManager#disconnect}, or the sender has become 332 * unreachable. 333 * <p> 334 * When disconnected, the sender can no longer send {@link Payload} to this client. 335 */ 336 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 337 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onDisconnected(@onNull OccupantZoneInfo senderZone)338 public void onDisconnected(@NonNull OccupantZoneInfo senderZone) { 339 assertPlatformVersionAtLeastU(); 340 } 341 342 /** Accepts the connection request from {@code senderZone}. */ 343 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 344 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) acceptConnection(@onNull OccupantZoneInfo senderZone)345 public final void acceptConnection(@NonNull OccupantZoneInfo senderZone) { 346 assertPlatformVersionAtLeastU(); 347 try { 348 mBackendConnectionResponder.acceptConnection(senderZone); 349 } catch (RemoteException e) { 350 throw e.rethrowAsRuntimeException(); 351 } 352 } 353 354 /** 355 * Rejects the connection request from {@code senderZone}. 356 * 357 * @param rejectionReason the reason for rejection. It could be a predefined value ( 358 * {@link CarOccupantConnectionManager#CONNECTION_ERROR_LONG_VERSION_NOT_MATCH}, 359 * {@link CarOccupantConnectionManager#CONNECTION_ERROR_SIGNATURE_NOT_MATCH}, 360 * {@link CarOccupantConnectionManager#CONNECTION_ERROR_USER_REJECTED}), or app-defined 361 * value that is larger than {@link 362 * CarOccupantConnectionManager#CONNECTION_ERROR_PREDEFINED_MAXIMUM_VALUE}. 363 */ 364 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 365 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) rejectConnection(@onNull OccupantZoneInfo senderZone, int rejectionReason)366 public final void rejectConnection(@NonNull OccupantZoneInfo senderZone, int rejectionReason) { 367 assertPlatformVersionAtLeastU(); 368 try { 369 mBackendConnectionResponder.rejectConnection(senderZone, rejectionReason); 370 } catch (RemoteException e) { 371 throw e.rethrowAsRuntimeException(); 372 } 373 } 374 375 /** 376 * Forwards the {@code payload} to the given receiver endpoint in this client. 377 * <p> 378 * Note: different receiver endpoints in the same client app are identified by their IDs, 379 * while different sender endpoints in the same client app are treated as the same sender. 380 * If the senders need to differentiate themselves, they can put the identity info into the 381 * {@code payload} it sends. 382 * 383 * @param senderZone the occupant zone that the Payload was sent from 384 * @param receiverEndpointId the ID of the receiver endpoint 385 * @param payload the Payload 386 * @return whether the Payload has been forwarded to the receiver endpoint 387 */ 388 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 389 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) forwardPayload(@onNull OccupantZoneInfo senderZone, @NonNull String receiverEndpointId, @NonNull Payload payload)390 public final boolean forwardPayload(@NonNull OccupantZoneInfo senderZone, 391 @NonNull String receiverEndpointId, 392 @NonNull Payload payload) { 393 assertPlatformVersionAtLeastU(); 394 IPayloadCallback callback = mReceiverEndpointMap.get(receiverEndpointId); 395 if (callback == null) { 396 Slogf.e(TAG, "The receiver endpoint has been unregistered: %s", receiverEndpointId); 397 return false; 398 } 399 try { 400 callback.onPayloadReceived(senderZone, receiverEndpointId, payload); 401 return true; 402 } catch (RemoteException e) { 403 throw e.rethrowAsRuntimeException(); 404 } 405 } 406 407 /** 408 * Returns an unmodifiable set containing all the IDs of the receiver endpoints. Returns an 409 * empty set if there is no receiver endpoint registered. 410 */ 411 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 412 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 413 @NonNull getAllReceiverEndpoints()414 public final Set<String> getAllReceiverEndpoints() { 415 assertPlatformVersionAtLeastU(); 416 return mReceiverEndpointMap.keySet(); 417 } 418 419 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 420 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 421 @Override onStartCommand(@onNull Intent intent, int flags, int startId)422 public int onStartCommand(@NonNull Intent intent, int flags, int startId) { 423 assertPlatformVersionAtLeastU(); 424 return START_STICKY; 425 } 426 427 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 428 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 429 @Override dump(@ullable FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args)430 public void dump(@Nullable FileDescriptor fd, @NonNull PrintWriter writer, 431 @Nullable String[] args) { 432 assertPlatformVersionAtLeastU(); 433 writer.println("*AbstractReceiverService*"); 434 writer.printf("%smReceiverEndpointMap:\n", INDENTATION_2); 435 for (int i = 0; i < mReceiverEndpointMap.size(); i++) { 436 String id = mReceiverEndpointMap.keyAt(i); 437 IPayloadCallback callback = mReceiverEndpointMap.valueAt(i); 438 writer.printf("%s%s, callback:%s\n", INDENTATION_4, id, callback); 439 } 440 writer.printf("%smBackendConnectionResponder:%s\n", INDENTATION_2, 441 mBackendConnectionResponder); 442 writer.printf("%smBackendReceiver:%s\n", INDENTATION_2, mBackendReceiver); 443 } 444 } 445