1 /*
2  * Copyright (C) 2017 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;
18 
19 import android.car.vms.IVmsPublisherClient;
20 import android.car.vms.IVmsPublisherService;
21 import android.car.vms.IVmsSubscriberClient;
22 import android.car.vms.VmsLayer;
23 import android.car.vms.VmsLayersOffering;
24 import android.car.vms.VmsSubscriptionState;
25 import android.content.Context;
26 import android.os.Binder;
27 import android.os.IBinder;
28 import android.os.RemoteException;
29 import android.util.ArrayMap;
30 import android.util.Log;
31 
32 import com.android.car.stats.CarStatsService;
33 import com.android.car.vms.VmsBrokerService;
34 import com.android.car.vms.VmsClientManager;
35 import com.android.internal.annotations.VisibleForTesting;
36 
37 import java.io.PrintWriter;
38 import java.util.Collections;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.function.IntSupplier;
42 
43 
44 /**
45  * Receives HAL updates by implementing VmsHalService.VmsHalListener.
46  * Binds to publishers and configures them to use this service.
47  * Notifies publishers of subscription changes.
48  */
49 public class VmsPublisherService implements CarServiceBase {
50     private static final boolean DBG = false;
51     private static final String TAG = "VmsPublisherService";
52 
53     private final Context mContext;
54     private final CarStatsService mStatsService;
55     private final VmsBrokerService mBrokerService;
56     private final VmsClientManager mClientManager;
57     private final IntSupplier mGetCallingUid;
58     private final Map<String, PublisherProxy> mPublisherProxies = Collections.synchronizedMap(
59             new ArrayMap<>());
60 
VmsPublisherService( Context context, CarStatsService statsService, VmsBrokerService brokerService, VmsClientManager clientManager)61     VmsPublisherService(
62             Context context,
63             CarStatsService statsService,
64             VmsBrokerService brokerService,
65             VmsClientManager clientManager) {
66         this(context, statsService, brokerService, clientManager, Binder::getCallingUid);
67     }
68 
69     @VisibleForTesting
VmsPublisherService( Context context, CarStatsService statsService, VmsBrokerService brokerService, VmsClientManager clientManager, IntSupplier getCallingUid)70     VmsPublisherService(
71             Context context,
72             CarStatsService statsService,
73             VmsBrokerService brokerService,
74             VmsClientManager clientManager,
75             IntSupplier getCallingUid) {
76         mContext = context;
77         mStatsService = statsService;
78         mBrokerService = brokerService;
79         mClientManager = clientManager;
80         mGetCallingUid = getCallingUid;
81 
82         mClientManager.setPublisherService(this);
83     }
84 
85     @Override
init()86     public void init() {}
87 
88     @Override
release()89     public void release() {
90         mPublisherProxies.values().forEach(PublisherProxy::unregister);
91         mPublisherProxies.clear();
92     }
93 
94     @Override
dump(PrintWriter writer)95     public void dump(PrintWriter writer) {
96         writer.println("*" + getClass().getSimpleName() + "*");
97         writer.println("mPublisherProxies: " + mPublisherProxies.size());
98     }
99 
100     /**
101      * Called when a client connection is established or re-established.
102      *
103      * @param publisherName    String that uniquely identifies the service and user.
104      * @param publisherClient The client's communication channel.
105      */
onClientConnected(String publisherName, IVmsPublisherClient publisherClient)106     public void onClientConnected(String publisherName, IVmsPublisherClient publisherClient) {
107         if (DBG) Log.d(TAG, "onClientConnected: " + publisherName);
108         IBinder publisherToken = new Binder();
109 
110         PublisherProxy publisherProxy = new PublisherProxy(publisherName, publisherToken,
111                 publisherClient);
112         publisherProxy.register();
113         try {
114             publisherClient.setVmsPublisherService(publisherToken, publisherProxy);
115         } catch (Throwable e) {
116             Log.e(TAG, "unable to configure publisher: " + publisherName, e);
117             return;
118         }
119 
120         PublisherProxy existingProxy = mPublisherProxies.put(publisherName, publisherProxy);
121         if (existingProxy != null) {
122             existingProxy.unregister();
123         }
124     }
125 
126     /**
127      * Called when a client connection is terminated.
128      *
129      * @param publisherName String that uniquely identifies the service and user.
130      */
onClientDisconnected(String publisherName)131     public void onClientDisconnected(String publisherName) {
132         if (DBG) Log.d(TAG, "onClientDisconnected: " + publisherName);
133         PublisherProxy proxy = mPublisherProxies.remove(publisherName);
134         if (proxy != null) {
135             proxy.unregister();
136         }
137     }
138 
139     private class PublisherProxy extends IVmsPublisherService.Stub implements
140             VmsBrokerService.PublisherListener {
141         private final String mName;
142         private final IBinder mToken;
143         private final IVmsPublisherClient mPublisherClient;
144         private boolean mConnected;
145 
PublisherProxy(String name, IBinder token, IVmsPublisherClient publisherClient)146         PublisherProxy(String name, IBinder token,
147                 IVmsPublisherClient publisherClient) {
148             this.mName = name;
149             this.mToken = token;
150             this.mPublisherClient = publisherClient;
151         }
152 
register()153         void register() {
154             if (DBG) Log.d(TAG, "register: " + mName);
155             mConnected = true;
156             mBrokerService.addPublisherListener(this);
157         }
158 
unregister()159         void unregister() {
160             if (DBG) Log.d(TAG, "unregister: " + mName);
161             mConnected = false;
162             mBrokerService.removePublisherListener(this);
163             mBrokerService.removeDeadPublisher(mToken);
164         }
165 
166         @Override
setLayersOffering(IBinder token, VmsLayersOffering offering)167         public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
168             assertPermission(token);
169             mBrokerService.setPublisherLayersOffering(token, offering);
170         }
171 
172         @Override
publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload)173         public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
174             assertPermission(token);
175             if (DBG) {
176                 Log.d(TAG, String.format("Publishing to %s as %d (%s)", layer, publisherId, mName));
177             }
178 
179             if (layer == null) {
180                 return;
181             }
182 
183             int payloadLength = payload != null ? payload.length : 0;
184             mStatsService.getVmsClientLogger(mGetCallingUid.getAsInt())
185                     .logPacketSent(layer, payloadLength);
186 
187             // Send the message to subscribers
188             Set<IVmsSubscriberClient> listeners =
189                     mBrokerService.getSubscribersForLayerFromPublisher(layer, publisherId);
190 
191             if (DBG) Log.d(TAG, String.format("Number of subscribers: %d", listeners.size()));
192 
193             if (listeners.size() == 0) {
194                 // A negative UID signals that the packet had zero subscribers
195                 mStatsService.getVmsClientLogger(-1)
196                         .logPacketDropped(layer, payloadLength);
197             }
198 
199             for (IVmsSubscriberClient listener : listeners) {
200                 int subscriberUid = mClientManager.getSubscriberUid(listener);
201                 try {
202                     listener.onVmsMessageReceived(layer, payload);
203                     mStatsService.getVmsClientLogger(subscriberUid)
204                             .logPacketReceived(layer, payloadLength);
205                 } catch (RemoteException ex) {
206                     mStatsService.getVmsClientLogger(subscriberUid)
207                             .logPacketDropped(layer, payloadLength);
208                     String subscriberName = mClientManager.getPackageName(listener);
209                     Log.e(TAG, String.format("Unable to publish to listener: %s", subscriberName));
210                 }
211             }
212         }
213 
214         @Override
getSubscriptions()215         public VmsSubscriptionState getSubscriptions() {
216             assertPermission();
217             return mBrokerService.getSubscriptionState();
218         }
219 
220         @Override
getPublisherId(byte[] publisherInfo)221         public int getPublisherId(byte[] publisherInfo) {
222             assertPermission();
223             return mBrokerService.getPublisherId(publisherInfo);
224         }
225 
226         @Override
onSubscriptionChange(VmsSubscriptionState subscriptionState)227         public void onSubscriptionChange(VmsSubscriptionState subscriptionState) {
228             try {
229                 mPublisherClient.onVmsSubscriptionChange(subscriptionState);
230             } catch (Throwable e) {
231                 Log.e(TAG, String.format("Unable to send subscription state to: %s", mName), e);
232             }
233         }
234 
assertPermission(IBinder publisherToken)235         private void assertPermission(IBinder publisherToken) {
236             if (mToken != publisherToken) {
237                 throw new SecurityException("Invalid publisher token");
238             }
239             assertPermission();
240         }
241 
assertPermission()242         private void assertPermission() {
243             if (!mConnected) {
244                 throw new SecurityException("Publisher has been disconnected");
245             }
246             ICarImpl.assertVmsPublisherPermission(mContext);
247         }
248     }
249 }
250