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