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.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.ServiceConnection; 31 import android.content.pm.PackageInfo; 32 import android.content.pm.PackageManager; 33 import android.os.Binder; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.os.UserHandle; 39 import android.text.TextUtils; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.Log; 43 44 import com.android.car.hal.VmsHalService; 45 import com.android.car.hal.VmsHalService.VmsHalPublisherListener; 46 47 import java.io.PrintWriter; 48 import java.util.Arrays; 49 import java.util.Map; 50 import java.util.Set; 51 52 /** 53 * Receives HAL updates by implementing VmsHalService.VmsHalListener. 54 * Binds to publishers and configures them to use this service. 55 * Notifies publishers of subscription changes. 56 */ 57 public class VmsPublisherService extends IVmsPublisherService.Stub implements CarServiceBase { 58 private static final boolean DBG = true; 59 private static final String TAG = "VmsPublisherService"; 60 61 private static final int MSG_HAL_SUBSCRIPTION_CHANGED = 1; 62 63 private final Context mContext; 64 private final VmsHalService mHal; 65 private final Map<String, PublisherConnection> mPublisherConnectionMap = new ArrayMap<>(); 66 private final Map<String, IVmsPublisherClient> mPublisherMap = new ArrayMap<>(); 67 private final Set<String> mSafePermissions; 68 private final Handler mHandler = new EventHandler(); 69 private final VmsHalPublisherListener mHalPublisherListener; 70 71 private BroadcastReceiver mBootCompleteReceiver; 72 VmsPublisherService(Context context, VmsHalService hal)73 public VmsPublisherService(Context context, VmsHalService hal) { 74 mContext = context; 75 mHal = hal; 76 77 mHalPublisherListener = subscriptionState -> mHandler.sendMessage( 78 mHandler.obtainMessage(MSG_HAL_SUBSCRIPTION_CHANGED, subscriptionState)); 79 80 // Load permissions that can be granted to publishers. 81 mSafePermissions = new ArraySet<>( 82 Arrays.asList(mContext.getResources().getStringArray(R.array.vmsSafePermissions))); 83 } 84 85 // Implements CarServiceBase interface. 86 @Override init()87 public void init() { 88 mHal.addPublisherListener(mHalPublisherListener); 89 90 if (isTestEnvironment()) { 91 Log.d(TAG, "Running under test environment"); 92 bindToAllPublishers(); 93 } else { 94 mBootCompleteReceiver = new BroadcastReceiver() { 95 @Override 96 public void onReceive(Context context, Intent intent) { 97 if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) { 98 onLockedBootCompleted(); 99 } else { 100 Log.e(TAG, "Unexpected action received: " + intent); 101 } 102 } 103 }; 104 105 mContext.registerReceiver(mBootCompleteReceiver, 106 new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED)); 107 } 108 } 109 bindToAllPublishers()110 private void bindToAllPublishers() { 111 String[] publisherNames = mContext.getResources().getStringArray( 112 R.array.vmsPublisherClients); 113 if (DBG) Log.d(TAG, "Publishers found: " + publisherNames.length); 114 115 for (String publisherName : publisherNames) { 116 if (TextUtils.isEmpty(publisherName)) { 117 Log.e(TAG, "empty publisher name"); 118 continue; 119 } 120 ComponentName name = ComponentName.unflattenFromString(publisherName); 121 if (name == null) { 122 Log.e(TAG, "invalid publisher name: " + publisherName); 123 continue; 124 } 125 126 if (!mContext.getPackageManager().isPackageAvailable(name.getPackageName())) { 127 Log.w(TAG, "VMS publisher not installed: " + publisherName); 128 continue; 129 } 130 131 bind(name); 132 } 133 } 134 135 @Override release()136 public void release() { 137 if (mBootCompleteReceiver != null) { 138 mContext.unregisterReceiver(mBootCompleteReceiver); 139 mBootCompleteReceiver = null; 140 } 141 mHal.removePublisherListener(mHalPublisherListener); 142 143 for (PublisherConnection connection : mPublisherConnectionMap.values()) { 144 mContext.unbindService(connection); 145 } 146 mPublisherConnectionMap.clear(); 147 mPublisherMap.clear(); 148 } 149 150 @Override dump(PrintWriter writer)151 public void dump(PrintWriter writer) { 152 writer.println("*" + getClass().getSimpleName() + "*"); 153 writer.println("mSafePermissions: " + mSafePermissions); 154 writer.println("mPublisherMap:" + mPublisherMap); 155 writer.println("mPublisherConnectionMap:" + mPublisherConnectionMap); 156 } 157 158 /* Called in arbitrary binder thread */ 159 @Override setLayersOffering(IBinder token, VmsLayersOffering offering)160 public void setLayersOffering(IBinder token, VmsLayersOffering offering) { 161 mHal.setPublisherLayersOffering(token, offering); 162 } 163 164 /* Called in arbitrary binder thread */ 165 @Override publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload)166 public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) { 167 if (DBG) { 168 Log.d(TAG, "Publishing for layer: " + layer); 169 } 170 ICarImpl.assertVmsPublisherPermission(mContext); 171 172 // Send the message to application listeners. 173 Set<IVmsSubscriberClient> listeners = 174 mHal.getSubscribersForLayerFromPublisher(layer, publisherId); 175 176 if (DBG) { 177 Log.d(TAG, "Number of subscribed apps: " + listeners.size()); 178 } 179 for (IVmsSubscriberClient listener : listeners) { 180 try { 181 listener.onVmsMessageReceived(layer, payload); 182 } catch (RemoteException ex) { 183 Log.e(TAG, "unable to publish to listener: " + listener); 184 } 185 } 186 187 // Send the message to HAL 188 if (mHal.isHalSubscribed(layer)) { 189 Log.d(TAG, "HAL is subscribed"); 190 mHal.setDataMessage(layer, payload); 191 } else { 192 Log.d(TAG, "HAL is NOT subscribed"); 193 } 194 } 195 196 /* Called in arbitrary binder thread */ 197 @Override getSubscriptions()198 public VmsSubscriptionState getSubscriptions() { 199 ICarImpl.assertVmsPublisherPermission(mContext); 200 return mHal.getSubscriptionState(); 201 } 202 203 /* Called in arbitrary binder thread */ 204 @Override getPublisherId(byte[] publisherInfo)205 public int getPublisherId(byte[] publisherInfo) { 206 ICarImpl.assertVmsPublisherPermission(mContext); 207 return mHal.getPublisherId(publisherInfo); 208 } 209 onLockedBootCompleted()210 private void onLockedBootCompleted() { 211 if (DBG) Log.i(TAG, "onLockedBootCompleted"); 212 213 bindToAllPublishers(); 214 } 215 216 /** 217 * This method is only invoked by VmsHalService.notifyPublishers which is synchronized. 218 * Therefore this method only sees a non-decreasing sequence. 219 */ handleHalSubscriptionChanged(VmsSubscriptionState subscriptionState)220 private void handleHalSubscriptionChanged(VmsSubscriptionState subscriptionState) { 221 // Send the message to application listeners. 222 for (IVmsPublisherClient client : mPublisherMap.values()) { 223 try { 224 client.onVmsSubscriptionChange(subscriptionState); 225 } catch (RemoteException ex) { 226 Log.e(TAG, "unable to send notification to: " + client, ex); 227 } 228 } 229 } 230 231 /** 232 * Tries to bind to a publisher. 233 * 234 * @param name publisher component name (e.g. android.car.vms.logger/.LoggingService). 235 */ bind(ComponentName name)236 private void bind(ComponentName name) { 237 String publisherName = name.flattenToString(); 238 if (DBG) { 239 Log.d(TAG, "binding to: " + publisherName); 240 } 241 242 if (mPublisherConnectionMap.containsKey(publisherName)) { 243 // Already registered, nothing to do. 244 return; 245 } 246 grantPermissions(name); 247 Intent intent = new Intent(); 248 intent.setComponent(name); 249 PublisherConnection connection = new PublisherConnection(name); 250 if (mContext.bindServiceAsUser(intent, connection, 251 Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) { 252 mPublisherConnectionMap.put(publisherName, connection); 253 } else { 254 Log.e(TAG, "unable to bind to: " + publisherName); 255 } 256 } 257 258 /** 259 * Removes the publisher and associated connection. 260 * 261 * @param name publisher component name (e.g. android.car.vms.Logger). 262 */ unbind(ComponentName name)263 private void unbind(ComponentName name) { 264 String publisherName = name.flattenToString(); 265 if (DBG) { 266 Log.d(TAG, "unbinding from: " + publisherName); 267 } 268 269 boolean found = mPublisherMap.remove(publisherName) != null; 270 if (found) { 271 PublisherConnection connection = mPublisherConnectionMap.get(publisherName); 272 mContext.unbindService(connection); 273 mPublisherConnectionMap.remove(publisherName); 274 } else { 275 Log.e(TAG, "unbind: unknown publisher." + publisherName); 276 } 277 } 278 grantPermissions(ComponentName component)279 private void grantPermissions(ComponentName component) { 280 final PackageManager packageManager = mContext.getPackageManager(); 281 final String packageName = component.getPackageName(); 282 PackageInfo packageInfo; 283 try { 284 packageInfo = packageManager.getPackageInfo(packageName, 285 PackageManager.GET_PERMISSIONS); 286 } catch (PackageManager.NameNotFoundException e) { 287 Log.e(TAG, "Error getting package info for " + packageName, e); 288 return; 289 } 290 if (packageInfo.requestedPermissions == null) return; 291 for (String permission : packageInfo.requestedPermissions) { 292 if (!mSafePermissions.contains(permission)) { 293 continue; 294 } 295 if (packageManager.checkPermission(permission, packageName) 296 == PackageManager.PERMISSION_GRANTED) { 297 continue; 298 } 299 try { 300 packageManager.grantRuntimePermission(packageName, permission, 301 UserHandle.SYSTEM); 302 Log.d(TAG, "Permission " + permission + " granted to " + packageName); 303 } catch (SecurityException | IllegalArgumentException e) { 304 Log.e(TAG, "Error while trying to grant " + permission + " to " + packageName, 305 e); 306 } 307 } 308 } 309 isTestEnvironment()310 private boolean isTestEnvironment() { 311 // If the context is derived from other package it means we're running under 312 // environment. 313 return !TextUtils.equals(mContext.getBasePackageName(), mContext.getPackageName()); 314 } 315 316 class PublisherConnection implements ServiceConnection { 317 private final IBinder mToken = new Binder(); 318 private final ComponentName mName; 319 PublisherConnection(ComponentName name)320 PublisherConnection(ComponentName name) { 321 mName = name; 322 } 323 324 private final Runnable mBindRunnable = new Runnable() { 325 @Override 326 public void run() { 327 Log.d(TAG, "delayed binding for: " + mName); 328 bind(mName); 329 } 330 }; 331 332 /** 333 * Once the service binds to a publisher service, the publisher binder is added to 334 * mPublisherMap 335 * and the publisher is configured to use this service. 336 */ 337 @Override onServiceConnected(ComponentName name, IBinder binder)338 public void onServiceConnected(ComponentName name, IBinder binder) { 339 if (DBG) { 340 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); 341 } 342 IVmsPublisherClient service = IVmsPublisherClient.Stub.asInterface(binder); 343 mPublisherMap.put(name.flattenToString(), service); 344 try { 345 service.setVmsPublisherService(mToken, VmsPublisherService.this); 346 } catch (RemoteException e) { 347 Log.e(TAG, "unable to configure publisher: " + name, e); 348 } 349 } 350 351 /** 352 * Tries to rebind to the publisher service. 353 */ 354 @Override onServiceDisconnected(ComponentName name)355 public void onServiceDisconnected(ComponentName name) { 356 String publisherName = name.flattenToString(); 357 Log.d(TAG, "onServiceDisconnected, name: " + publisherName); 358 359 int millisecondsToWait = mContext.getResources().getInteger( 360 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher); 361 if (!mName.flattenToString().equals(name.flattenToString())) { 362 throw new IllegalArgumentException( 363 "Mismatch on publisherConnection. Expected: " + mName + " Got: " + name); 364 } 365 mHandler.postDelayed(mBindRunnable, millisecondsToWait); 366 367 unbind(name); 368 } 369 } 370 371 private class EventHandler extends Handler { 372 @Override handleMessage(Message msg)373 public void handleMessage(Message msg) { 374 switch (msg.what) { 375 case MSG_HAL_SUBSCRIPTION_CHANGED: 376 handleHalSubscriptionChanged((VmsSubscriptionState) msg.obj); 377 return; 378 } 379 super.handleMessage(msg); 380 } 381 } 382 } 383