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