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.annotation.FutureFeature; 20 import android.car.vms.IVmsSubscriberClient; 21 import android.car.vms.IVmsPublisherClient; 22 import android.car.vms.IVmsPublisherService; 23 import android.car.vms.VmsLayer; 24 import android.car.vms.VmsLayersOffering; 25 import android.car.vms.VmsSubscriptionState; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.os.Binder; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import com.android.car.hal.VmsHalService; 36 import com.android.internal.annotations.GuardedBy; 37 import java.io.PrintWriter; 38 import java.lang.ref.WeakReference; 39 import java.util.ArrayList; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 45 /** 46 * + Receives HAL updates by implementing VmsHalService.VmsHalListener. 47 * + Binds to publishers and configures them to use this service. 48 * + Notifies publishers of subscription changes. 49 */ 50 @FutureFeature 51 public class VmsPublisherService extends IVmsPublisherService.Stub 52 implements CarServiceBase, VmsHalService.VmsHalPublisherListener { 53 private static final boolean DBG = true; 54 private static final String TAG = "VmsPublisherService"; 55 56 private final Context mContext; 57 private final VmsHalService mHal; 58 private final VmsPublisherManager mPublisherManager; 59 private final Map<IBinder, VmsLayersOffering> mRawOffering = new HashMap<>(); 60 61 public VmsPublisherService(Context context, VmsHalService hal) { 62 mContext = context; 63 mHal = hal; 64 mPublisherManager = new VmsPublisherManager(this); 65 } 66 67 // Implements CarServiceBase interface. 68 @Override 69 public void init() { 70 mHal.addPublisherListener(this); 71 // Launch publishers. 72 String[] publisherNames = mContext.getResources().getStringArray( 73 R.array.vmsPublisherClients); 74 for (String publisherName : publisherNames) { 75 if (TextUtils.isEmpty(publisherName)) { 76 Log.e(TAG, "empty publisher name"); 77 continue; 78 } 79 ComponentName name = ComponentName.unflattenFromString(publisherName); 80 if (name == null) { 81 Log.e(TAG, "invalid publisher name: " + publisherName); 82 } 83 mPublisherManager.bind(name); 84 } 85 } 86 87 @Override 88 public void release() { 89 mPublisherManager.release(); 90 mHal.removePublisherListener(this); 91 } 92 93 @Override 94 public void dump(PrintWriter writer) { 95 } 96 97 @Override 98 public void setLayersOffering(IBinder token, VmsLayersOffering offering) { 99 // Store the raw dependencies 100 mRawOffering.put(token, offering); 101 102 //TODO(asafro): Calculate the new available layers 103 104 //TODO(asafro): Notify the subscribers that there is a change in availability 105 } 106 107 // Implements IVmsPublisherService interface. 108 @Override 109 public void publish(IBinder token, VmsLayer layer, byte[] payload) { 110 if (DBG) { 111 Log.d(TAG, "Publishing for layer: " + layer); 112 } 113 ICarImpl.assertVmsPublisherPermission(mContext); 114 115 // Send the message to application listeners. 116 Set<IVmsSubscriberClient> listeners = mHal.getListeners(layer); 117 118 if (DBG) { 119 Log.d(TAG, "Number of subscribed apps: " + listeners.size()); 120 } 121 for (IVmsSubscriberClient listener : listeners) { 122 try { 123 listener.onVmsMessageReceived(layer, payload); 124 } catch (RemoteException ex) { 125 Log.e(TAG, "unable to publish to listener: " + listener); 126 } 127 } 128 129 // Send the message to HAL 130 if (mHal.isHalSubscribed(layer)) { 131 Log.d(TAG, "HAL is subscribed"); 132 mHal.setDataMessage(layer, payload); 133 } else { 134 Log.d(TAG, "HAL is NOT subscribed"); 135 } 136 } 137 138 @Override 139 public VmsSubscriptionState getSubscriptions() { 140 ICarImpl.assertVmsPublisherPermission(mContext); 141 return mHal.getSubscriptionState(); 142 } 143 144 // Implements VmsHalListener interface 145 /** 146 * This method is only invoked by VmsHalService.notifyPublishers which is synchronized. 147 * Therefore this method only sees a non-decreasing sequence. 148 */ 149 @Override 150 public void onChange(VmsSubscriptionState subscriptionState) { 151 // Send the message to application listeners. 152 for (IVmsPublisherClient client : mPublisherManager.getClients()) { 153 try { 154 client.onVmsSubscriptionChange(subscriptionState); 155 } catch (RemoteException ex) { 156 Log.e(TAG, "unable to send notification to: " + client, ex); 157 } 158 } 159 } 160 161 /** 162 * Keeps track of publishers that are using this service. 163 */ 164 private static class VmsPublisherManager { 165 /** 166 * Allows to modify mPublisherMap and mPublisherConnectionMap as a single unit. 167 */ 168 private final Object mLock = new Object(); 169 @GuardedBy("mLock") 170 private final Map<String, PublisherConnection> mPublisherConnectionMap = new HashMap<>(); 171 @GuardedBy("mLock") 172 private final Map<String, IVmsPublisherClient> mPublisherMap = new HashMap<>(); 173 private final WeakReference<VmsPublisherService> mPublisherService; 174 175 public VmsPublisherManager(VmsPublisherService publisherService) { 176 mPublisherService = new WeakReference<>(publisherService); 177 } 178 179 /** 180 * Tries to bind to a publisher. 181 * 182 * @param name publisher component name (e.g. android.car.vms.logger/.LoggingService). 183 */ 184 public void bind(ComponentName name) { 185 VmsPublisherService publisherService = mPublisherService.get(); 186 if (publisherService == null) return; 187 String publisherName = name.flattenToString(); 188 if (DBG) { 189 Log.d(TAG, "binding to: " + publisherName); 190 } 191 synchronized (mLock) { 192 if (mPublisherConnectionMap.containsKey(publisherName)) { 193 // Already registered, nothing to do. 194 return; 195 } 196 Intent intent = new Intent(); 197 intent.setComponent(name); 198 PublisherConnection connection = new PublisherConnection(); 199 if (publisherService.mContext.bindService(intent, connection, 200 Context.BIND_AUTO_CREATE)) { 201 mPublisherConnectionMap.put(publisherName, connection); 202 } else { 203 Log.e(TAG, "unable to bind to: " + publisherName); 204 } 205 } 206 } 207 208 /** 209 * Removes the publisher and associated connection. 210 * 211 * @param name publisher component name (e.g. android.car.vms.Logger). 212 */ 213 public void unbind(ComponentName name) { 214 VmsPublisherService publisherService = mPublisherService.get(); 215 if (publisherService == null) return; 216 String publisherName = name.flattenToString(); 217 if (DBG) { 218 Log.d(TAG, "unbinding from: " + publisherName); 219 } 220 synchronized (mLock) { 221 boolean found = mPublisherMap.remove(publisherName) != null; 222 if (found) { 223 PublisherConnection connection = mPublisherConnectionMap.get(publisherName); 224 publisherService.mContext.unbindService(connection); 225 mPublisherConnectionMap.remove(publisherName); 226 } else { 227 Log.e(TAG, "unbind: unknown publisher." + publisherName); 228 } 229 } 230 } 231 232 /** 233 * Returns the list of publishers currently registered. 234 * 235 * @return list of publishers. 236 */ 237 public List<IVmsPublisherClient> getClients() { 238 synchronized (mLock) { 239 return new ArrayList<>(mPublisherMap.values()); 240 } 241 } 242 243 public void release() { 244 VmsPublisherService publisherService = mPublisherService.get(); 245 if (publisherService == null) return; 246 for (PublisherConnection connection : mPublisherConnectionMap.values()) { 247 publisherService.mContext.unbindService(connection); 248 } 249 mPublisherConnectionMap.clear(); 250 mPublisherMap.clear(); 251 } 252 253 class PublisherConnection implements ServiceConnection { 254 255 private final IBinder mToken = new Binder(); 256 257 /** 258 * Once the service binds to a publisher service, the publisher binder is added to 259 * mPublisherMap 260 * and the publisher is configured to use this service. 261 */ 262 @Override 263 public void onServiceConnected(ComponentName name, IBinder binder) { 264 VmsPublisherService publisherService = mPublisherService.get(); 265 if (publisherService == null) return; 266 if (DBG) { 267 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); 268 } 269 IVmsPublisherClient service = IVmsPublisherClient.Stub.asInterface(binder); 270 synchronized (mLock) { 271 mPublisherMap.put(name.flattenToString(), service); 272 } 273 try { 274 service.setVmsPublisherService(mToken, publisherService); 275 } catch (RemoteException e) { 276 Log.e(TAG, "unable to configure publisher: " + name); 277 } 278 } 279 280 /** 281 * Tries to rebind to the publisher service. 282 */ 283 @Override 284 public void onServiceDisconnected(ComponentName name) { 285 String publisherName = name.flattenToString(); 286 Log.d(TAG, "onServiceDisconnected, name: " + publisherName); 287 VmsPublisherManager.this.unbind(name); 288 VmsPublisherManager.this.bind(name); 289 } 290 } 291 } 292 } 293