• 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.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