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 com.android.car.vms; 18 19 import static com.android.car.CarServiceUtils.assertAnyVmsPermission; 20 import static com.android.car.CarServiceUtils.assertVmsPublisherPermission; 21 import static com.android.car.CarServiceUtils.assertVmsSubscriberPermission; 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import android.car.builtin.util.Slogf; 25 import android.car.vms.IVmsBrokerService; 26 import android.car.vms.IVmsClientCallback; 27 import android.car.vms.VmsAssociatedLayer; 28 import android.car.vms.VmsAvailableLayers; 29 import android.car.vms.VmsLayer; 30 import android.car.vms.VmsLayerDependency; 31 import android.car.vms.VmsLayersOffering; 32 import android.car.vms.VmsProviderInfo; 33 import android.car.vms.VmsRegistrationInfo; 34 import android.car.vms.VmsSubscriptionState; 35 import android.content.Context; 36 import android.content.pm.PackageManager; 37 import android.os.Binder; 38 import android.os.IBinder; 39 import android.os.RemoteException; 40 import android.os.SharedMemory; 41 import android.util.ArrayMap; 42 import android.util.ArraySet; 43 44 import com.android.car.CarLog; 45 import com.android.car.CarServiceBase; 46 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 47 import com.android.car.internal.util.FunctionalUtils.ThrowingConsumer; 48 import com.android.car.internal.util.IndentingPrintWriter; 49 import com.android.car.stats.CarStatsService; 50 import com.android.car.stats.VmsClientLogger; 51 import com.android.internal.annotations.GuardedBy; 52 import com.android.internal.annotations.VisibleForTesting; 53 54 import java.util.ArrayList; 55 import java.util.Collection; 56 import java.util.Collections; 57 import java.util.Comparator; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.function.IntSupplier; 62 import java.util.stream.Collectors; 63 64 /** 65 * Message broker service for routing Vehicle Map Service messages between clients. 66 * 67 * This service is also responsible for tracking VMS client connections and broadcasting 68 * notifications to clients about layer offering or subscription state changes. 69 */ 70 public class VmsBrokerService extends IVmsBrokerService.Stub implements CarServiceBase { 71 private static final boolean DBG = false; 72 private static final String TAG = CarLog.tagFor(VmsBrokerService.class); 73 74 private final Context mContext; 75 private final PackageManager mPackageManager; 76 private final CarStatsService mStatsService; 77 private final IntSupplier mGetCallingUid; 78 79 private final VmsProviderInfoStore mProviderInfoStore = new VmsProviderInfoStore(); 80 private final VmsLayerAvailability mAvailableLayers = new VmsLayerAvailability(); 81 82 private final Object mLock = new Object(); 83 @GuardedBy("mLock") 84 private final Map<IBinder /* clientToken */, VmsClientInfo> mClientMap = new ArrayMap<>(); 85 @GuardedBy("mLock") 86 private Set<VmsLayersOffering> mAllOfferings = Collections.emptySet(); 87 @GuardedBy("mLock") 88 private VmsSubscriptionState mSubscriptionState = new VmsSubscriptionState(0, 89 Collections.emptySet(), Collections.emptySet()); 90 VmsBrokerService(Context context, CarStatsService statsService)91 public VmsBrokerService(Context context, CarStatsService statsService) { 92 this(context, statsService, Binder::getCallingUid); 93 } 94 95 @VisibleForTesting VmsBrokerService( Context context, CarStatsService statsService, IntSupplier getCallingUid)96 VmsBrokerService( 97 Context context, 98 CarStatsService statsService, 99 IntSupplier getCallingUid) { 100 mContext = context; 101 mPackageManager = context.getPackageManager(); 102 mStatsService = statsService; 103 mGetCallingUid = getCallingUid; 104 } 105 106 @Override init()107 public void init() { 108 } 109 110 @Override release()111 public void release() { 112 } 113 114 @Override 115 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)116 public void dump(IndentingPrintWriter writer) { 117 writer.println("*" + TAG + "*"); 118 synchronized (mLock) { 119 writer.println("mAvailableLayers: " + mAvailableLayers.getAvailableLayers()); 120 writer.println(); 121 writer.println("mSubscriptionState: " + mSubscriptionState); 122 writer.println(); 123 writer.println("mClientMap:"); 124 mClientMap.values().stream() 125 .sorted(Comparator.comparingInt(VmsClientInfo::getUid)) 126 .forEach(client -> client.dump(writer, " ")); 127 } 128 } 129 130 @Override registerClient(IBinder clientToken, IVmsClientCallback callback, boolean legacyClient)131 public VmsRegistrationInfo registerClient(IBinder clientToken, IVmsClientCallback callback, 132 boolean legacyClient) { 133 assertAnyVmsPermission(mContext); 134 int clientUid = mGetCallingUid.getAsInt(); 135 String clientPackage = mPackageManager.getNameForUid(clientUid); 136 if (DBG) Slogf.d(TAG, "registerClient uid: " + clientUid + " package: " + clientPackage); 137 138 mStatsService.getVmsClientLogger(clientUid) 139 .logConnectionState(VmsClientLogger.ConnectionState.CONNECTED); 140 141 IBinder.DeathRecipient deathRecipient; 142 try { 143 deathRecipient = () -> unregisterClient(clientToken, 144 VmsClientLogger.ConnectionState.DISCONNECTED); 145 callback.asBinder().linkToDeath(deathRecipient, 0); 146 } catch (RemoteException e) { 147 mStatsService.getVmsClientLogger(clientUid) 148 .logConnectionState(VmsClientLogger.ConnectionState.DISCONNECTED); 149 throw new IllegalStateException("Client callback is already dead"); 150 } 151 152 synchronized (mLock) { 153 mClientMap.put(clientToken, new VmsClientInfo(clientUid, clientPackage, callback, 154 legacyClient, deathRecipient)); 155 return new VmsRegistrationInfo( 156 mAvailableLayers.getAvailableLayers(), 157 mSubscriptionState); 158 } 159 } 160 161 @Override unregisterClient(IBinder clientToken)162 public void unregisterClient(IBinder clientToken) { 163 assertAnyVmsPermission(mContext); 164 unregisterClient(clientToken, VmsClientLogger.ConnectionState.TERMINATED); 165 } 166 167 @Override getProviderInfo(IBinder clientToken, int providerId)168 public VmsProviderInfo getProviderInfo(IBinder clientToken, int providerId) { 169 assertAnyVmsPermission(mContext); 170 getClient(clientToken); // Assert that the client is registered 171 return new VmsProviderInfo(mProviderInfoStore.getProviderInfo(providerId)); 172 } 173 174 @Override setSubscriptions(IBinder clientToken, List<VmsAssociatedLayer> layers)175 public void setSubscriptions(IBinder clientToken, List<VmsAssociatedLayer> layers) { 176 assertVmsSubscriberPermission(mContext); 177 getClient(clientToken).setSubscriptions(layers); 178 updateSubscriptionState(); 179 } 180 181 @Override setMonitoringEnabled(IBinder clientToken, boolean enabled)182 public void setMonitoringEnabled(IBinder clientToken, boolean enabled) { 183 assertVmsSubscriberPermission(mContext); 184 getClient(clientToken).setMonitoringEnabled(enabled); 185 } 186 187 @Override registerProvider(IBinder clientToken, VmsProviderInfo providerInfo)188 public int registerProvider(IBinder clientToken, VmsProviderInfo providerInfo) { 189 assertVmsPublisherPermission(mContext); 190 VmsClientInfo client = getClient(clientToken); 191 int providerId; 192 synchronized (mLock) { 193 providerId = mProviderInfoStore.getProviderId(providerInfo.getDescription()); 194 } 195 client.addProviderId(providerId); 196 return providerId; 197 } 198 199 @Override setProviderOfferings(IBinder clientToken, int providerId, List<VmsLayerDependency> offerings)200 public void setProviderOfferings(IBinder clientToken, int providerId, 201 List<VmsLayerDependency> offerings) { 202 assertVmsPublisherPermission(mContext); 203 VmsClientInfo client = getClient(clientToken); 204 if (!client.hasProviderId(providerId) && !client.isLegacyClient()) { 205 throw new IllegalArgumentException("Client not registered to offer layers as " 206 + providerId); 207 } 208 if (client.setProviderOfferings(providerId, offerings)) { 209 updateAvailableLayers(); 210 } 211 } 212 213 @Override publishPacket(IBinder clientToken, int providerId, VmsLayer layer, byte[] packet)214 public void publishPacket(IBinder clientToken, int providerId, VmsLayer layer, byte[] packet) { 215 assertVmsPublisherPermission(mContext); 216 deliverToSubscribers(clientToken, providerId, layer, packet.length, 217 callback -> callback.onPacketReceived(providerId, layer, packet)); 218 } 219 220 @Override publishLargePacket(IBinder clientToken, int providerId, VmsLayer layer, SharedMemory packet)221 public void publishLargePacket(IBinder clientToken, int providerId, VmsLayer layer, 222 SharedMemory packet) { 223 try (SharedMemory largePacket = packet) { 224 assertVmsPublisherPermission(mContext); 225 deliverToSubscribers(clientToken, providerId, layer, packet.getSize(), 226 callback -> callback.onLargePacketReceived(providerId, layer, largePacket)); 227 } 228 } 229 deliverToSubscribers(IBinder clientToken, int providerId, VmsLayer layer, int packetLength, ThrowingConsumer<IVmsClientCallback> callbackConsumer)230 private void deliverToSubscribers(IBinder clientToken, int providerId, VmsLayer layer, 231 int packetLength, ThrowingConsumer<IVmsClientCallback> callbackConsumer) { 232 VmsClientInfo client = getClient(clientToken); 233 if (!client.hasOffering(providerId, layer) && !client.isLegacyClient()) { 234 throw new IllegalArgumentException("Client does not offer " + layer + " as " 235 + providerId); 236 } 237 238 mStatsService.getVmsClientLogger(client.getUid()) 239 .logPacketSent(layer, packetLength); 240 241 Collection<VmsClientInfo> subscribers; 242 synchronized (mLock) { 243 subscribers = mClientMap.values().stream() 244 .filter(subscriber -> subscriber.isSubscribed(providerId, layer)) 245 .collect(Collectors.toList()); 246 } 247 248 if (DBG) Slogf.d(TAG, "Number of subscribers: %d", subscribers.size()); 249 250 if (subscribers.isEmpty()) { 251 // A negative UID signals that the packet had zero subscribers 252 mStatsService.getVmsClientLogger(-1).logPacketDropped(layer, packetLength); 253 return; 254 } 255 256 for (VmsClientInfo subscriber : subscribers) { 257 try { 258 callbackConsumer.accept(subscriber.getCallback()); 259 mStatsService.getVmsClientLogger(subscriber.getUid()) 260 .logPacketReceived(layer, packetLength); 261 } catch (RuntimeException e) { 262 mStatsService.getVmsClientLogger(subscriber.getUid()) 263 .logPacketDropped(layer, packetLength); 264 Slogf.e(TAG, e, "Unable to publish to listener: %s", subscriber.getPackageName()); 265 } 266 } 267 } 268 unregisterClient(IBinder clientToken, int connectionState)269 private void unregisterClient(IBinder clientToken, int connectionState) { 270 VmsClientInfo client; 271 synchronized (mLock) { 272 client = mClientMap.remove(clientToken); 273 } 274 if (client != null) { 275 client.getCallback().asBinder().unlinkToDeath(client.getDeathRecipient(), 0); 276 mStatsService.getVmsClientLogger(client.getUid()) 277 .logConnectionState(connectionState); 278 updateAvailableLayers(); 279 updateSubscriptionState(); 280 } 281 } 282 getClient(IBinder clientToken)283 private VmsClientInfo getClient(IBinder clientToken) { 284 synchronized (mLock) { 285 VmsClientInfo client = mClientMap.get(clientToken); 286 if (client == null) { 287 throw new IllegalStateException("Unknown client token"); 288 } 289 return client; 290 } 291 } 292 getActiveClients()293 private Collection<VmsClientInfo> getActiveClients() { 294 synchronized (mLock) { 295 return new ArrayList<>(mClientMap.values()); 296 } 297 } 298 updateAvailableLayers()299 private void updateAvailableLayers() { 300 synchronized (mLock) { 301 // Fuse layer offerings 302 Set<VmsLayersOffering> allOfferings = mClientMap.values().stream() 303 .map(VmsClientInfo::getAllOfferings) 304 .flatMap(Collection::stream) 305 .collect(Collectors.toCollection(ArraySet::new)); 306 307 // Ignore update if offerings are unchanged 308 if (mAllOfferings.equals(allOfferings)) { 309 return; 310 } 311 312 // Update offerings and compute available layers 313 mAllOfferings = allOfferings; 314 mAvailableLayers.setPublishersOffering(allOfferings); 315 } 316 notifyOfAvailabilityChange(mAvailableLayers.getAvailableLayers()); 317 } 318 notifyOfAvailabilityChange(VmsAvailableLayers availableLayers)319 private void notifyOfAvailabilityChange(VmsAvailableLayers availableLayers) { 320 Slogf.i(TAG, "Notifying clients of layer availability change: " + availableLayers); 321 for (VmsClientInfo client : getActiveClients()) { 322 try { 323 client.getCallback().onLayerAvailabilityChanged(availableLayers); 324 } catch (RemoteException e) { 325 Slogf.w(TAG, "onLayersAvailabilityChanged failed: " + client.getPackageName(), e); 326 } 327 } 328 } 329 updateSubscriptionState()330 private void updateSubscriptionState() { 331 VmsSubscriptionState subscriptionState; 332 synchronized (mLock) { 333 Set<VmsLayer> layerSubscriptions = new ArraySet<>(); 334 Map<VmsLayer, Set<Integer>> layerAndProviderSubscriptions = new ArrayMap<>(); 335 // Fuse subscriptions 336 for (VmsClientInfo client : mClientMap.values()) { 337 layerSubscriptions.addAll(client.getLayerSubscriptions()); 338 client.getLayerAndProviderSubscriptions().forEach((layer, providerIds) -> { 339 Set<Integer> providerSubscriptions = 340 layerAndProviderSubscriptions.computeIfAbsent( 341 layer, 342 ignored -> new ArraySet<>()); 343 providerSubscriptions.addAll(providerIds); 344 }); 345 } 346 347 // Remove global layer subscriptions from provider-specific subscription state 348 layerSubscriptions.forEach(layerAndProviderSubscriptions::remove); 349 350 // Transform provider-specific subscriptions into VmsAssociatedLayers 351 Set<VmsAssociatedLayer> associatedLayers = 352 layerAndProviderSubscriptions.entrySet().stream() 353 .map(entry -> new VmsAssociatedLayer(entry.getKey(), entry.getValue())) 354 .collect(Collectors.toCollection(ArraySet::new)); 355 356 // Ignore update if subscriptions are unchanged 357 if (mSubscriptionState.getLayers().equals(layerSubscriptions) 358 && mSubscriptionState.getAssociatedLayers().equals(associatedLayers)) { 359 return; 360 } 361 362 // Update subscription state 363 subscriptionState = new VmsSubscriptionState( 364 mSubscriptionState.getSequenceNumber() + 1, 365 layerSubscriptions, 366 associatedLayers); 367 mSubscriptionState = subscriptionState; 368 } 369 // Notify clients of update 370 notifyOfSubscriptionChange(subscriptionState); 371 } 372 notifyOfSubscriptionChange(VmsSubscriptionState subscriptionState)373 private void notifyOfSubscriptionChange(VmsSubscriptionState subscriptionState) { 374 Slogf.i(TAG, "Notifying clients of subscription state change: " + subscriptionState); 375 for (VmsClientInfo client : getActiveClients()) { 376 try { 377 client.getCallback().onSubscriptionStateChanged(subscriptionState); 378 } catch (RemoteException e) { 379 Slogf.w(TAG, "onSubscriptionStateChanged failed: " + client.getPackageName(), e); 380 } 381 } 382 } 383 } 384