• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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