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