1 /* 2 * Copyright (C) 2019 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 android.car.Car; 20 import android.car.vms.IVmsPublisherClient; 21 import android.car.vms.IVmsSubscriberClient; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ServiceInfo; 28 import android.os.Binder; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.Process; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.os.UserManager; 36 import android.util.ArrayMap; 37 import android.util.Log; 38 39 import com.android.car.CarServiceBase; 40 import com.android.car.R; 41 import com.android.car.VmsPublisherService; 42 import com.android.car.hal.VmsHalService; 43 import com.android.car.stats.CarStatsService; 44 import com.android.car.stats.VmsClientLogger; 45 import com.android.car.stats.VmsClientLogger.ConnectionState; 46 import com.android.car.user.CarUserService; 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 50 import java.io.PrintWriter; 51 import java.util.Collection; 52 import java.util.Map; 53 import java.util.NoSuchElementException; 54 import java.util.function.IntSupplier; 55 import java.util.stream.Collectors; 56 import java.util.stream.Stream; 57 58 /** 59 * Manages service connections lifecycle for VMS publisher clients. 60 * 61 * Binds to system-level clients at boot and creates/destroys bindings for userspace clients 62 * according to the Android user lifecycle. 63 */ 64 public class VmsClientManager implements CarServiceBase { 65 private static final boolean DBG = false; 66 private static final String TAG = "VmsClientManager"; 67 private static final String HAL_CLIENT_NAME = "HalClient"; 68 private static final String UNKNOWN_PACKAGE = "UnknownPackage"; 69 70 private final Context mContext; 71 private final PackageManager mPackageManager; 72 private final UserManager mUserManager; 73 private final CarUserService mUserService; 74 private final CarStatsService mStatsService; 75 private final Handler mHandler; 76 private final IntSupplier mGetCallingUid; 77 private final int mMillisBeforeRebind; 78 79 private final Object mLock = new Object(); 80 81 @GuardedBy("mLock") 82 private final VmsBrokerService mBrokerService; 83 @GuardedBy("mLock") 84 private VmsPublisherService mPublisherService; 85 86 @GuardedBy("mLock") 87 private final Map<String, PublisherConnection> mSystemClients = new ArrayMap<>(); 88 @GuardedBy("mLock") 89 private IVmsPublisherClient mHalClient; 90 @GuardedBy("mLock") 91 private boolean mSystemUserUnlocked; 92 93 @GuardedBy("mLock") 94 private final Map<String, PublisherConnection> mCurrentUserClients = new ArrayMap<>(); 95 @GuardedBy("mLock") 96 private int mCurrentUser; 97 98 @GuardedBy("mLock") 99 private final Map<IBinder, SubscriberConnection> mSubscribers = new ArrayMap<>(); 100 101 @VisibleForTesting 102 final Runnable mSystemUserUnlockedListener = () -> { 103 synchronized (mLock) { 104 mSystemUserUnlocked = true; 105 } 106 bindToSystemClients(); 107 }; 108 109 @VisibleForTesting 110 public final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() { 111 @Override 112 public void onSwitchUser(int userId) { 113 synchronized (mLock) { 114 if (mCurrentUser != userId) { 115 mCurrentUser = userId; 116 terminate(mCurrentUserClients); 117 terminate(mSubscribers.values().stream() 118 .filter(subscriber -> subscriber.mUserId != mCurrentUser) 119 .filter(subscriber -> subscriber.mUserId != UserHandle.USER_SYSTEM)); 120 } 121 } 122 bindToUserClients(); 123 } 124 125 @Override 126 public void onUserLockChanged(int userId, boolean unlocked) { 127 synchronized (mLock) { 128 if (mCurrentUser == userId && unlocked) { 129 bindToUserClients(); 130 } 131 } 132 } 133 }; 134 135 /** 136 * Constructor for client manager. 137 * 138 * @param context Context to use for registering receivers and binding services. 139 * @param statsService Service for logging client metrics. 140 * @param userService User service for registering system unlock listener. 141 * @param brokerService Service managing the VMS publisher/subscriber state. 142 * @param halService Service providing the HAL client interface 143 */ VmsClientManager(Context context, CarStatsService statsService, CarUserService userService, VmsBrokerService brokerService, VmsHalService halService)144 public VmsClientManager(Context context, CarStatsService statsService, 145 CarUserService userService, VmsBrokerService brokerService, 146 VmsHalService halService) { 147 this(context, statsService, userService, brokerService, halService, 148 new Handler(Looper.getMainLooper()), Binder::getCallingUid); 149 } 150 151 @VisibleForTesting VmsClientManager(Context context, CarStatsService statsService, CarUserService userService, VmsBrokerService brokerService, VmsHalService halService, Handler handler, IntSupplier getCallingUid)152 VmsClientManager(Context context, CarStatsService statsService, 153 CarUserService userService, VmsBrokerService brokerService, 154 VmsHalService halService, Handler handler, IntSupplier getCallingUid) { 155 mContext = context; 156 mPackageManager = context.getPackageManager(); 157 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 158 mStatsService = statsService; 159 mUserService = userService; 160 mCurrentUser = UserHandle.USER_NULL; 161 mBrokerService = brokerService; 162 mHandler = handler; 163 mGetCallingUid = getCallingUid; 164 mMillisBeforeRebind = context.getResources().getInteger( 165 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher); 166 167 halService.setClientManager(this); 168 } 169 170 /** 171 * Registers the publisher service for connection callbacks. 172 * 173 * @param publisherService Publisher service to register. 174 */ setPublisherService(VmsPublisherService publisherService)175 public void setPublisherService(VmsPublisherService publisherService) { 176 synchronized (mLock) { 177 mPublisherService = publisherService; 178 } 179 } 180 181 @Override init()182 public void init() { 183 mUserService.runOnUser0Unlock(mSystemUserUnlockedListener); 184 mUserService.addUserCallback(mUserCallback); 185 } 186 187 @Override release()188 public void release() { 189 mUserService.removeUserCallback(mUserCallback); 190 synchronized (mLock) { 191 if (mHalClient != null) { 192 mPublisherService.onClientDisconnected(HAL_CLIENT_NAME); 193 } 194 terminate(mSystemClients); 195 terminate(mCurrentUserClients); 196 terminate(mSubscribers.values().stream()); 197 } 198 } 199 200 @Override dump(PrintWriter writer)201 public void dump(PrintWriter writer) { 202 writer.println("*" + getClass().getSimpleName() + "*"); 203 synchronized (mLock) { 204 writer.println("mCurrentUser:" + mCurrentUser); 205 writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected")); 206 writer.println("mSystemClients:"); 207 dumpConnections(writer, mSystemClients); 208 209 writer.println("mCurrentUserClients:"); 210 dumpConnections(writer, mCurrentUserClients); 211 212 writer.println("mSubscribers:"); 213 for (SubscriberConnection subscriber : mSubscribers.values()) { 214 writer.printf("\t%s\n", subscriber); 215 } 216 } 217 } 218 219 220 /** 221 * Adds a subscriber for connection tracking. 222 * 223 * @param subscriberClient Subscriber client to track. 224 */ addSubscriber(IVmsSubscriberClient subscriberClient)225 public void addSubscriber(IVmsSubscriberClient subscriberClient) { 226 if (subscriberClient == null) { 227 Log.e(TAG, "Trying to add a null subscriber: " 228 + getCallingPackage(mGetCallingUid.getAsInt())); 229 throw new IllegalArgumentException("subscriber cannot be null."); 230 } 231 232 synchronized (mLock) { 233 IBinder subscriberBinder = subscriberClient.asBinder(); 234 if (mSubscribers.containsKey(subscriberBinder)) { 235 // Already registered 236 return; 237 } 238 239 int callingUid = mGetCallingUid.getAsInt(); 240 int subscriberUserId = UserHandle.getUserId(callingUid); 241 if (subscriberUserId != mCurrentUser && subscriberUserId != UserHandle.USER_SYSTEM) { 242 throw new SecurityException("Caller must be foreground user or system"); 243 } 244 245 SubscriberConnection subscriber = new SubscriberConnection( 246 subscriberClient, callingUid, getCallingPackage(callingUid), subscriberUserId); 247 if (DBG) Log.d(TAG, "Registering subscriber: " + subscriber); 248 try { 249 subscriberBinder.linkToDeath(subscriber, 0); 250 } catch (RemoteException e) { 251 throw new IllegalStateException("Subscriber already dead: " + subscriber, e); 252 } 253 mSubscribers.put(subscriberBinder, subscriber); 254 } 255 } 256 257 /** 258 * Removes a subscriber for connection tracking and expires its subscriptions. 259 * 260 * @param subscriberClient Subscriber client to remove. 261 */ removeSubscriber(IVmsSubscriberClient subscriberClient)262 public void removeSubscriber(IVmsSubscriberClient subscriberClient) { 263 synchronized (mLock) { 264 SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder()); 265 if (subscriber != null) { 266 subscriber.terminate(); 267 } 268 } 269 } 270 271 /** 272 * Returns all active subscriber clients. 273 */ getAllSubscribers()274 public Collection<IVmsSubscriberClient> getAllSubscribers() { 275 synchronized (mLock) { 276 return mSubscribers.values().stream() 277 .map(subscriber -> subscriber.mClient) 278 .collect(Collectors.toList()); 279 } 280 } 281 282 /** 283 * Gets the application UID associated with a subscriber client. 284 */ getSubscriberUid(IVmsSubscriberClient subscriberClient)285 public int getSubscriberUid(IVmsSubscriberClient subscriberClient) { 286 synchronized (mLock) { 287 SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder()); 288 return subscriber != null ? subscriber.mUid : Process.INVALID_UID; 289 } 290 } 291 292 /** 293 * Gets the package name for a given subscriber client. 294 */ getPackageName(IVmsSubscriberClient subscriberClient)295 public String getPackageName(IVmsSubscriberClient subscriberClient) { 296 synchronized (mLock) { 297 SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder()); 298 return subscriber != null ? subscriber.mPackageName : UNKNOWN_PACKAGE; 299 } 300 } 301 302 /** 303 * Registers the HAL client connections. 304 */ onHalConnected(IVmsPublisherClient publisherClient, IVmsSubscriberClient subscriberClient)305 public void onHalConnected(IVmsPublisherClient publisherClient, 306 IVmsSubscriberClient subscriberClient) { 307 synchronized (mLock) { 308 mHalClient = publisherClient; 309 mPublisherService.onClientConnected(HAL_CLIENT_NAME, mHalClient); 310 mSubscribers.put(subscriberClient.asBinder(), 311 new SubscriberConnection(subscriberClient, Process.myUid(), HAL_CLIENT_NAME, 312 UserHandle.USER_SYSTEM)); 313 } 314 mStatsService.getVmsClientLogger(Process.myUid()) 315 .logConnectionState(ConnectionState.CONNECTED); 316 } 317 318 /** 319 * 320 */ onHalDisconnected()321 public void onHalDisconnected() { 322 synchronized (mLock) { 323 if (mHalClient != null) { 324 mPublisherService.onClientDisconnected(HAL_CLIENT_NAME); 325 mStatsService.getVmsClientLogger(Process.myUid()) 326 .logConnectionState(ConnectionState.DISCONNECTED); 327 } 328 mHalClient = null; 329 terminate(mSubscribers.values().stream() 330 .filter(subscriber -> HAL_CLIENT_NAME.equals(subscriber.mPackageName))); 331 } 332 } 333 dumpConnections(PrintWriter writer, Map<String, PublisherConnection> connectionMap)334 private void dumpConnections(PrintWriter writer, 335 Map<String, PublisherConnection> connectionMap) { 336 for (PublisherConnection connection : connectionMap.values()) { 337 writer.printf("\t%s: %s\n", 338 connection.mName.getPackageName(), 339 connection.mIsBound ? "connected" : "disconnected"); 340 } 341 } 342 bindToSystemClients()343 private void bindToSystemClients() { 344 String[] clientNames = mContext.getResources().getStringArray( 345 R.array.vmsPublisherSystemClients); 346 synchronized (mLock) { 347 if (!mSystemUserUnlocked) { 348 return; 349 } 350 Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)"); 351 for (String clientName : clientNames) { 352 bind(mSystemClients, clientName, UserHandle.SYSTEM); 353 } 354 } 355 } 356 bindToUserClients()357 private void bindToUserClients() { 358 bindToSystemClients(); // Bind system clients on user switch, if they are not already bound. 359 synchronized (mLock) { 360 if (mCurrentUser == UserHandle.USER_NULL) { 361 Log.e(TAG, "Unknown user in foreground."); 362 return; 363 } 364 // To avoid the risk of double-binding, clients running as the system user must only 365 // ever be bound in bindToSystemClients(). 366 if (mCurrentUser == UserHandle.USER_SYSTEM) { 367 Log.e(TAG, "System user in foreground. Userspace clients will not be bound."); 368 return; 369 } 370 371 if (!mUserManager.isUserUnlockingOrUnlocked(mCurrentUser)) { 372 Log.i(TAG, "Waiting for foreground user " + mCurrentUser + " to be unlocked."); 373 return; 374 } 375 376 String[] clientNames = mContext.getResources().getStringArray( 377 R.array.vmsPublisherUserClients); 378 Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)"); 379 UserHandle currentUserHandle = UserHandle.of(mCurrentUser); 380 for (String clientName : clientNames) { 381 bind(mCurrentUserClients, clientName, currentUserHandle); 382 } 383 } 384 } 385 bind(Map<String, PublisherConnection> connectionMap, String clientName, UserHandle userHandle)386 private void bind(Map<String, PublisherConnection> connectionMap, String clientName, 387 UserHandle userHandle) { 388 if (connectionMap.containsKey(clientName)) { 389 Log.i(TAG, "Already bound: " + clientName); 390 return; 391 } 392 393 ComponentName name = ComponentName.unflattenFromString(clientName); 394 if (name == null) { 395 Log.e(TAG, "Invalid client name: " + clientName); 396 return; 397 } 398 399 ServiceInfo serviceInfo; 400 try { 401 serviceInfo = mContext.getPackageManager().getServiceInfo(name, 402 PackageManager.MATCH_DIRECT_BOOT_AUTO); 403 } catch (PackageManager.NameNotFoundException e) { 404 Log.w(TAG, "Client not installed: " + clientName); 405 return; 406 } 407 408 VmsClientLogger statsLog = mStatsService.getVmsClientLogger( 409 UserHandle.getUid(userHandle.getIdentifier(), serviceInfo.applicationInfo.uid)); 410 411 if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) { 412 Log.e(TAG, "Client service: " + clientName 413 + " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission"); 414 statsLog.logConnectionState(ConnectionState.CONNECTION_ERROR); 415 return; 416 } 417 418 PublisherConnection connection = new PublisherConnection(name, userHandle, statsLog); 419 if (connection.bind()) { 420 Log.i(TAG, "Client bound: " + connection); 421 connectionMap.put(clientName, connection); 422 } else { 423 Log.e(TAG, "Binding failed: " + connection); 424 } 425 } 426 terminate(Map<String, PublisherConnection> connectionMap)427 private void terminate(Map<String, PublisherConnection> connectionMap) { 428 connectionMap.values().forEach(PublisherConnection::terminate); 429 connectionMap.clear(); 430 } 431 432 class PublisherConnection implements ServiceConnection { 433 private final ComponentName mName; 434 private final UserHandle mUser; 435 private final String mFullName; 436 private final VmsClientLogger mStatsLog; 437 private boolean mIsBound = false; 438 private boolean mIsTerminated = false; 439 private boolean mRebindScheduled = false; 440 private IVmsPublisherClient mClientService; 441 PublisherConnection(ComponentName name, UserHandle user, VmsClientLogger statsLog)442 PublisherConnection(ComponentName name, UserHandle user, VmsClientLogger statsLog) { 443 mName = name; 444 mUser = user; 445 mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier(); 446 mStatsLog = statsLog; 447 } 448 bind()449 synchronized boolean bind() { 450 if (mIsBound) { 451 return true; 452 } 453 if (mIsTerminated) { 454 return false; 455 } 456 mStatsLog.logConnectionState(ConnectionState.CONNECTING); 457 458 if (DBG) Log.d(TAG, "binding: " + mFullName); 459 Intent intent = new Intent(); 460 intent.setComponent(mName); 461 try { 462 mIsBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, 463 mHandler, mUser); 464 } catch (SecurityException e) { 465 Log.e(TAG, "While binding " + mFullName, e); 466 } 467 468 if (!mIsBound) { 469 mStatsLog.logConnectionState(ConnectionState.CONNECTION_ERROR); 470 } 471 472 return mIsBound; 473 } 474 unbind()475 synchronized void unbind() { 476 if (!mIsBound) { 477 return; 478 } 479 480 if (DBG) Log.d(TAG, "unbinding: " + mFullName); 481 try { 482 mContext.unbindService(this); 483 } catch (Throwable t) { 484 Log.e(TAG, "While unbinding " + mFullName, t); 485 } 486 mIsBound = false; 487 } 488 scheduleRebind()489 synchronized void scheduleRebind() { 490 if (mRebindScheduled) { 491 return; 492 } 493 494 if (DBG) { 495 Log.d(TAG, 496 String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind)); 497 } 498 mHandler.postDelayed(this::doRebind, mMillisBeforeRebind); 499 mRebindScheduled = true; 500 } 501 doRebind()502 synchronized void doRebind() { 503 mRebindScheduled = false; 504 // Do not rebind if the connection has been terminated, or the client service has 505 // reconnected on its own. 506 if (mIsTerminated || mClientService != null) { 507 return; 508 } 509 510 Log.i(TAG, "Rebinding: " + mFullName); 511 // Ensure that the client is not bound before attempting to rebind. 512 // If the client is not currently bound, unbind() will have no effect. 513 unbind(); 514 bind(); 515 } 516 terminate()517 synchronized void terminate() { 518 if (DBG) Log.d(TAG, "terminating: " + mFullName); 519 mIsTerminated = true; 520 notifyOnDisconnect(ConnectionState.TERMINATED); 521 unbind(); 522 } 523 notifyOnDisconnect(int connectionState)524 synchronized void notifyOnDisconnect(int connectionState) { 525 if (mClientService != null) { 526 mPublisherService.onClientDisconnected(mFullName); 527 mClientService = null; 528 mStatsLog.logConnectionState(connectionState); 529 } 530 } 531 532 @Override onServiceConnected(ComponentName name, IBinder service)533 public void onServiceConnected(ComponentName name, IBinder service) { 534 if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName); 535 mClientService = IVmsPublisherClient.Stub.asInterface(service); 536 mPublisherService.onClientConnected(mFullName, mClientService); 537 mStatsLog.logConnectionState(ConnectionState.CONNECTED); 538 } 539 540 @Override onServiceDisconnected(ComponentName name)541 public void onServiceDisconnected(ComponentName name) { 542 if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName); 543 notifyOnDisconnect(ConnectionState.DISCONNECTED); 544 scheduleRebind(); 545 } 546 547 @Override onBindingDied(ComponentName name)548 public void onBindingDied(ComponentName name) { 549 if (DBG) Log.d(TAG, "onBindingDied: " + mFullName); 550 notifyOnDisconnect(ConnectionState.DISCONNECTED); 551 scheduleRebind(); 552 } 553 554 @Override toString()555 public String toString() { 556 return mFullName; 557 } 558 } 559 terminate(Stream<SubscriberConnection> subscribers)560 private void terminate(Stream<SubscriberConnection> subscribers) { 561 // Make a copy of the stream, so that terminate() doesn't cause a concurrent modification 562 subscribers.collect(Collectors.toList()).forEach(SubscriberConnection::terminate); 563 } 564 565 // If we're in a binder call, returns back the package name of the caller of the binder call. getCallingPackage(int uid)566 private String getCallingPackage(int uid) { 567 String packageName = mPackageManager.getNameForUid(uid); 568 if (packageName == null) { 569 return UNKNOWN_PACKAGE; 570 } else { 571 return packageName; 572 } 573 } 574 575 private class SubscriberConnection implements IBinder.DeathRecipient { 576 private final IVmsSubscriberClient mClient; 577 private final int mUid; 578 private final String mPackageName; 579 private final int mUserId; 580 SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName, int userId)581 SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName, 582 int userId) { 583 mClient = subscriberClient; 584 mUid = uid; 585 mPackageName = packageName; 586 mUserId = userId; 587 } 588 589 @Override binderDied()590 public void binderDied() { 591 if (DBG) Log.d(TAG, "Subscriber died: " + this); 592 terminate(); 593 } 594 595 @Override toString()596 public String toString() { 597 return mPackageName + " U=" + mUserId; 598 } 599 terminate()600 void terminate() { 601 if (DBG) Log.d(TAG, "Terminating subscriber: " + this); 602 synchronized (mLock) { 603 mBrokerService.removeDeadSubscriber(mClient); 604 IBinder subscriberBinder = mClient.asBinder(); 605 try { 606 subscriberBinder.unlinkToDeath(this, 0); 607 } catch (NoSuchElementException e) { 608 if (DBG) Log.d(TAG, "While unlinking subscriber binder for " + this, e); 609 } 610 mSubscribers.remove(subscriberBinder); 611 } 612 } 613 } 614 } 615