1 /*
2  * Copyright (C) 2020 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.vms;
18 
19 import static android.system.OsConstants.PROT_READ;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.car.Car;
26 import android.car.vms.VmsClientManager.VmsClientCallback;
27 import android.os.Binder;
28 import android.os.IBinder;
29 import android.os.RemoteException;
30 import android.os.SharedMemory;
31 import android.system.ErrnoException;
32 import android.util.Slog;
33 
34 import com.android.internal.annotations.GuardedBy;
35 
36 import java.lang.ref.WeakReference;
37 import java.nio.ByteBuffer;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Objects;
41 import java.util.Set;
42 import java.util.concurrent.Executor;
43 import java.util.function.BiConsumer;
44 import java.util.function.Consumer;
45 
46 /**
47  * API implementation for use by Vehicle Map Service clients.
48  *
49  * This API can be obtained by registering a callback with {@link VmsClientManager}.
50  *
51  * @hide
52  */
53 @SystemApi
54 public final class VmsClient {
55     private static final boolean DBG = false;
56     private static final String TAG = VmsClient.class.getSimpleName();
57 
58     private static final VmsAvailableLayers DEFAULT_AVAILABLE_LAYERS =
59             new VmsAvailableLayers(Collections.emptySet(), 0);
60     private static final VmsSubscriptionState DEFAULT_SUBSCRIPTIONS =
61             new VmsSubscriptionState(0, Collections.emptySet(), Collections.emptySet());
62     private static final int LARGE_PACKET_THRESHOLD = 16 * 1024; // 16 KB
63 
64     private final IVmsBrokerService mService;
65     private final Executor mExecutor;
66     private final VmsClientCallback mCallback;
67     private final boolean mLegacyClient;
68     private final IVmsClientCallback mClientCallback;
69     private final Consumer<RemoteException> mExceptionHandler;
70     private final IBinder mClientToken;
71 
72     private final Object mLock = new Object();
73     @GuardedBy("mLock")
74     private VmsAvailableLayers mAvailableLayers = DEFAULT_AVAILABLE_LAYERS;
75     @GuardedBy("mLock")
76     private VmsSubscriptionState mSubscriptionState = DEFAULT_SUBSCRIPTIONS;
77     @GuardedBy("mLock")
78     private boolean mMonitoringEnabled;
79 
80     /**
81      * @hide
82      */
VmsClient(IVmsBrokerService service, Executor executor, VmsClientCallback callback, boolean legacyClient, boolean autoCloseMemory, Consumer<RemoteException> exceptionHandler)83     public VmsClient(IVmsBrokerService service, Executor executor, VmsClientCallback callback,
84             boolean legacyClient, boolean autoCloseMemory,
85             Consumer<RemoteException> exceptionHandler) {
86         mService = service;
87         mExecutor = executor;
88         mCallback = callback;
89         mLegacyClient = legacyClient;
90         mClientCallback = new IVmsClientCallbackImpl(this, autoCloseMemory);
91         mExceptionHandler = exceptionHandler;
92         mClientToken = new Binder();
93     }
94 
95     /**
96      * Retrieves registered information about a Vehicle Map Service data provider.
97      *
98      * <p>Data layers may be associated with multiple providers in updates received via
99      * {@link VmsClientCallback#onLayerAvailabilityChanged(VmsAvailableLayers)}, and clients can use
100      * this query to determine a provider or subset of providers to subscribe to.
101      *
102      * @param providerId Provider ID
103      * @return Provider's registration information, or null if unavailable
104      */
105     @Nullable
106     @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
getProviderDescription(int providerId)107     public byte[] getProviderDescription(int providerId) {
108         if (DBG) Slog.d(TAG, "Getting provider information for " + providerId);
109         try {
110             return mService.getProviderInfo(mClientToken, providerId).getDescription();
111         } catch (RemoteException e) {
112             Slog.e(TAG, "While getting publisher information for " + providerId, e);
113             mExceptionHandler.accept(e);
114             return null;
115         }
116     }
117 
118     /**
119      * Sets this client's data layer subscriptions
120      *
121      * <p>Existing subscriptions for the client will be replaced.
122      *
123      * @param layers Data layers to be subscribed
124      */
125     @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
setSubscriptions(@onNull Set<VmsAssociatedLayer> layers)126     public void setSubscriptions(@NonNull Set<VmsAssociatedLayer> layers) {
127         if (DBG) Slog.d(TAG, "Setting subscriptions to " + layers);
128         try {
129             mService.setSubscriptions(mClientToken, new ArrayList<>(layers));
130         } catch (RemoteException e) {
131             Slog.e(TAG, "While setting subscriptions", e);
132             mExceptionHandler.accept(e);
133         }
134     }
135 
136     /**
137      * Enables the monitoring of Vehicle Map Service packets by this client.
138      *
139      * <p>If monitoring is enabled, the client will receive all packets, regardless of any
140      * subscriptions. Enabling monitoring does not affect the client's existing subscriptions.
141      */
142     @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
setMonitoringEnabled(boolean enabled)143     public void setMonitoringEnabled(boolean enabled) {
144         if (DBG) Slog.d(TAG, "Setting monitoring state to " + enabled);
145         try {
146             mService.setMonitoringEnabled(mClientToken, enabled);
147             synchronized (mLock) {
148                 mMonitoringEnabled = enabled;
149             }
150         } catch (RemoteException e) {
151             Slog.e(TAG, "While setting monitoring state to " + enabled, e);
152             mExceptionHandler.accept(e);
153         }
154     }
155 
156     /**
157      * Returns the current monitoring state of the client.
158      */
159     @RequiresPermission(Car.PERMISSION_VMS_SUBSCRIBER)
isMonitoringEnabled()160     public boolean isMonitoringEnabled() {
161         synchronized (mLock) {
162             return mMonitoringEnabled;
163         }
164     }
165 
166     /**
167      * Returns the most recently received data layer availability.
168      */
169     @NonNull
170     @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
getAvailableLayers()171     public VmsAvailableLayers getAvailableLayers() {
172         synchronized (mLock) {
173             return mAvailableLayers;
174         }
175     }
176 
177     /**
178      * Registers a data provider with the Vehicle Map Service.
179      *
180      * @param providerDescription Identifying information about the data provider
181      * @return Provider ID to use for setting offerings and publishing packets, or -1 on
182      * connection error
183      */
184     @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
registerProvider(@onNull byte[] providerDescription)185     public int registerProvider(@NonNull byte[] providerDescription) {
186         if (DBG) Slog.d(TAG, "Registering provider");
187         Objects.requireNonNull(providerDescription, "providerDescription cannot be null");
188         try {
189             return mService.registerProvider(mClientToken,
190                     new VmsProviderInfo(providerDescription));
191         } catch (RemoteException e) {
192             Slog.e(TAG, "While registering provider", e);
193             mExceptionHandler.accept(e);
194             return -1;
195         }
196     }
197 
198     /**
199      * Unregisters a data provider with the Vehicle Map Service.
200      *
201      * @param providerId Provider ID
202      */
203     @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
unregisterProvider(int providerId)204     public void unregisterProvider(int providerId) {
205         if (DBG) Slog.d(TAG, "Unregistering provider");
206         try {
207             setProviderOfferings(providerId, Collections.emptySet());
208         } catch (IllegalArgumentException e) {
209             Slog.e(TAG, "While unregistering provider " + providerId, e);
210         }
211     }
212 
213     /**
214      * Sets the data layer offerings for a provider registered through
215      * {@link #registerProvider(byte[])}.
216      *
217      * <p>Existing offerings for the provider ID will be replaced.
218      *
219      * @param providerId Provider ID
220      * @param offerings  Data layer offerings
221      * @throws IllegalArgumentException if the client has not registered the provider
222      */
223     @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
setProviderOfferings(int providerId, @NonNull Set<VmsLayerDependency> offerings)224     public void setProviderOfferings(int providerId, @NonNull Set<VmsLayerDependency> offerings) {
225         if (DBG) Slog.d(TAG, "Setting provider offerings for " + providerId);
226         Objects.requireNonNull(offerings, "offerings cannot be null");
227         try {
228             mService.setProviderOfferings(mClientToken, providerId, new ArrayList<>(offerings));
229         } catch (RemoteException e) {
230             Slog.e(TAG, "While setting provider offerings for " + providerId, e);
231             mExceptionHandler.accept(e);
232         }
233     }
234 
235     /**
236      * Publishes a Vehicle Maps Service packet.
237      *
238      * @param providerId Packet provider
239      * @param layer      Packet layer
240      * @param packet     Packet data
241      * @throws IllegalArgumentException if the client does not offer the layer as the provider
242      */
243     @RequiresPermission(Car.PERMISSION_VMS_PUBLISHER)
publishPacket(int providerId, @NonNull VmsLayer layer, @NonNull byte[] packet)244     public void publishPacket(int providerId, @NonNull VmsLayer layer, @NonNull byte[] packet) {
245         Objects.requireNonNull(layer, "layer cannot be null");
246         Objects.requireNonNull(packet, "packet cannot be null");
247         if (DBG) {
248             Slog.d(TAG, "Publishing packet as " + providerId + " (" + packet.length + " bytes)");
249         }
250         try {
251             if (packet.length < LARGE_PACKET_THRESHOLD) {
252                 mService.publishPacket(mClientToken, providerId, layer, packet);
253             } else {
254                 try (SharedMemory largePacket = packetToSharedMemory(packet)) {
255                     mService.publishLargePacket(mClientToken, providerId, layer,
256                             largePacket);
257                 }
258             }
259         } catch (RemoteException e) {
260             Slog.e(TAG, "While publishing packet as " + providerId);
261             mExceptionHandler.accept(e);
262         }
263     }
264 
265     /**
266      * Returns the most recently received data layer subscription state.
267      */
268     @NonNull
269     @RequiresPermission(anyOf = {Car.PERMISSION_VMS_PUBLISHER, Car.PERMISSION_VMS_SUBSCRIBER})
getSubscriptionState()270     public VmsSubscriptionState getSubscriptionState() {
271         synchronized (mLock) {
272             return mSubscriptionState;
273         }
274     }
275 
276     /**
277      * Registers this client with the Vehicle Map Service.
278      *
279      * @hide
280      */
register()281     public void register() throws RemoteException {
282         VmsRegistrationInfo registrationInfo = mService.registerClient(
283                 mClientToken, mClientCallback, mLegacyClient);
284         synchronized (mLock) {
285             mAvailableLayers = registrationInfo.getAvailableLayers();
286             mSubscriptionState = registrationInfo.getSubscriptionState();
287         }
288     }
289 
290     /**
291      * Unregisters this client from the Vehicle Map Service.
292      *
293      * @hide
294      */
unregister()295     public void unregister() throws RemoteException {
296         mService.unregisterClient(mClientToken);
297     }
298 
299     private static class IVmsClientCallbackImpl extends IVmsClientCallback.Stub {
300         private final WeakReference<VmsClient> mClient;
301         private final boolean mAutoCloseMemory;
302 
IVmsClientCallbackImpl(VmsClient client, boolean autoCloseMemory)303         private IVmsClientCallbackImpl(VmsClient client, boolean autoCloseMemory) {
304             mClient = new WeakReference<>(client);
305             mAutoCloseMemory = autoCloseMemory;
306         }
307 
308         @Override
onLayerAvailabilityChanged(VmsAvailableLayers availableLayers)309         public void onLayerAvailabilityChanged(VmsAvailableLayers availableLayers) {
310             if (DBG) Slog.d(TAG, "Received new layer availability: " + availableLayers);
311             executeCallback((client, callback) -> {
312                 synchronized (client.mLock) {
313                     client.mAvailableLayers = availableLayers;
314                 }
315                 callback.onLayerAvailabilityChanged(availableLayers);
316             });
317         }
318 
319         @Override
onSubscriptionStateChanged(VmsSubscriptionState subscriptionState)320         public void onSubscriptionStateChanged(VmsSubscriptionState subscriptionState) {
321             if (DBG) Slog.d(TAG, "Received new subscription state: " + subscriptionState);
322             executeCallback((client, callback) -> {
323                 synchronized (client.mLock) {
324                     client.mSubscriptionState = subscriptionState;
325                 }
326                 callback.onSubscriptionStateChanged(subscriptionState);
327             });
328         }
329 
330         @Override
onPacketReceived(int providerId, VmsLayer layer, byte[] packet)331         public void onPacketReceived(int providerId, VmsLayer layer, byte[] packet) {
332             if (DBG) {
333                 Slog.d(TAG, "Received packet from " + providerId + " for: " + layer
334                         + " (" + packet.length + " bytes)");
335             }
336             executeCallback((client, callback) ->
337                     callback.onPacketReceived(providerId, layer, packet));
338         }
339 
340         @Override
onLargePacketReceived(int providerId, VmsLayer layer, SharedMemory packet)341         public void onLargePacketReceived(int providerId, VmsLayer layer, SharedMemory packet) {
342             if (DBG) {
343                 Slog.d(TAG, "Received large packet from " + providerId + " for: " + layer
344                         + " (" + packet.getSize() + " bytes)");
345             }
346             byte[] largePacket;
347             if (mAutoCloseMemory) {
348                 try (SharedMemory autoClosedPacket = packet) {
349                     largePacket = sharedMemoryToPacket(autoClosedPacket);
350                 }
351             } else {
352                 largePacket = sharedMemoryToPacket(packet);
353             }
354             executeCallback((client, callback) ->
355                     callback.onPacketReceived(providerId, layer, largePacket));
356         }
357 
executeCallback(BiConsumer<VmsClient, VmsClientCallback> callbackOperation)358         private void executeCallback(BiConsumer<VmsClient, VmsClientCallback> callbackOperation) {
359             final VmsClient client = mClient.get();
360             if (client == null) {
361                 Slog.w(TAG, "VmsClient unavailable");
362                 return;
363             }
364 
365             long token = Binder.clearCallingIdentity();
366             try {
367                 client.mExecutor.execute(() -> callbackOperation.accept(client, client.mCallback));
368             } finally {
369                 Binder.restoreCallingIdentity(token);
370             }
371         }
372     }
373 
packetToSharedMemory(byte[] packet)374     private static SharedMemory packetToSharedMemory(byte[] packet) {
375         SharedMemory shm;
376         try {
377             shm = SharedMemory.create("VmsClient", packet.length);
378         } catch (ErrnoException e) {
379             throw new IllegalStateException("Failed to allocate shared memory", e);
380         }
381 
382         ByteBuffer buffer = null;
383         try {
384             buffer = shm.mapReadWrite();
385             buffer.put(packet);
386         } catch (ErrnoException e) {
387             shm.close();
388             throw new IllegalStateException("Failed to create write buffer", e);
389         } finally {
390             if (buffer != null) {
391                 SharedMemory.unmap(buffer);
392             }
393         }
394 
395         if (!shm.setProtect(PROT_READ)) {
396             shm.close();
397             throw new SecurityException("Failed to set read-only protection on shared memory");
398         }
399 
400         return shm;
401     }
402 
sharedMemoryToPacket(SharedMemory shm)403     private static byte[] sharedMemoryToPacket(SharedMemory shm) {
404         ByteBuffer buffer;
405         try {
406             buffer = shm.mapReadOnly();
407         } catch (ErrnoException e) {
408             throw new IllegalStateException("Failed to create read buffer", e);
409         }
410 
411         byte[] packet;
412         try {
413             packet = new byte[buffer.capacity()];
414             buffer.get(packet);
415         } finally {
416             SharedMemory.unmap(buffer);
417         }
418         return packet;
419     }
420 }
421