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