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