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