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