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.userlib.CarUserManagerHelper; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.ServiceConnection; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ServiceInfo; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.UserHandle; 33 import android.os.UserManager; 34 import android.util.ArrayMap; 35 import android.util.Log; 36 37 import com.android.car.CarServiceBase; 38 import com.android.car.R; 39 import com.android.car.hal.VmsHalService; 40 import com.android.car.user.CarUserService; 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.io.PrintWriter; 45 import java.util.ArrayList; 46 import java.util.Map; 47 import java.util.concurrent.atomic.AtomicLong; 48 49 /** 50 * Manages service connections lifecycle for VMS publisher clients. 51 * 52 * Binds to system-level clients at boot and creates/destroys bindings for userspace clients 53 * according to the Android user lifecycle. 54 */ 55 public class VmsClientManager implements CarServiceBase { 56 private static final boolean DBG = true; 57 private static final String TAG = "VmsClientManager"; 58 private static final String HAL_CLIENT_NAME = "VmsHalClient"; 59 60 /** 61 * Interface for receiving updates about client connections. 62 */ 63 public interface ConnectionListener { 64 /** 65 * Called when a client connection is established or re-established. 66 * 67 * @param clientName String that uniquely identifies the service and user. 68 * @param clientService The IBinder of the client's communication channel. 69 */ onClientConnected(String clientName, IBinder clientService)70 void onClientConnected(String clientName, IBinder clientService); 71 72 /** 73 * Called when a client connection is terminated. 74 * 75 * @param clientName String that uniquely identifies the service and user. 76 */ onClientDisconnected(String clientName)77 void onClientDisconnected(String clientName); 78 } 79 80 private final Context mContext; 81 private final Handler mHandler; 82 private final UserManager mUserManager; 83 private final CarUserService mUserService; 84 private final CarUserManagerHelper mUserManagerHelper; 85 private final int mMillisBeforeRebind; 86 87 @GuardedBy("mListeners") 88 private final ArrayList<ConnectionListener> mListeners = new ArrayList<>(); 89 @GuardedBy("mSystemClients") 90 private final Map<String, ClientConnection> mSystemClients = new ArrayMap<>(); 91 @GuardedBy("mSystemClients") 92 private IBinder mHalClient; 93 @GuardedBy("mSystemClients") 94 private boolean mSystemUserUnlocked; 95 96 @GuardedBy("mCurrentUserClients") 97 private final Map<String, ClientConnection> mCurrentUserClients = new ArrayMap<>(); 98 @GuardedBy("mCurrentUserClients") 99 private int mCurrentUser; 100 101 @GuardedBy("mRebindCounts") 102 private final Map<String, AtomicLong> mRebindCounts = new ArrayMap<>(); 103 104 @VisibleForTesting 105 final Runnable mSystemUserUnlockedListener = () -> { 106 synchronized (mSystemClients) { 107 mSystemUserUnlocked = true; 108 } 109 bindToSystemClients(); 110 }; 111 112 @VisibleForTesting 113 final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() { 114 @Override 115 public void onReceive(Context context, Intent intent) { 116 if (DBG) Log.d(TAG, "Received " + intent); 117 synchronized (mCurrentUserClients) { 118 int currentUserId = mUserManagerHelper.getCurrentForegroundUserId(); 119 if (mCurrentUser != currentUserId) { 120 terminate(mCurrentUserClients); 121 } 122 mCurrentUser = currentUserId; 123 124 if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction()) 125 || mUserManager.isUserUnlocked(mCurrentUser)) { 126 bindToSystemClients(); 127 bindToUserClients(); 128 } 129 } 130 } 131 }; 132 133 /** 134 * Constructor for client managers. 135 * 136 * @param context Context to use for registering receivers and binding services. 137 * @param userService User service for registering system unlock listener. 138 * @param userManagerHelper User manager for querying current user state. 139 * @param halService Service providing the HAL client interface 140 */ VmsClientManager(Context context, CarUserService userService, CarUserManagerHelper userManagerHelper, VmsHalService halService)141 public VmsClientManager(Context context, CarUserService userService, 142 CarUserManagerHelper userManagerHelper, VmsHalService halService) { 143 mContext = context; 144 mHandler = new Handler(Looper.getMainLooper()); 145 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 146 mUserService = userService; 147 mUserManagerHelper = userManagerHelper; 148 mMillisBeforeRebind = mContext.getResources().getInteger( 149 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher); 150 halService.setPublisherConnectionCallbacks(this::onHalConnected, this::onHalDisconnected); 151 } 152 153 @Override init()154 public void init() { 155 mUserService.runOnUser0Unlock(mSystemUserUnlockedListener); 156 157 IntentFilter userSwitchFilter = new IntentFilter(); 158 userSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED); 159 userSwitchFilter.addAction(Intent.ACTION_USER_UNLOCKED); 160 mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL, userSwitchFilter, null, 161 null); 162 } 163 164 @Override release()165 public void release() { 166 mContext.unregisterReceiver(mUserSwitchReceiver); 167 notifyListenersOnClientDisconnected(HAL_CLIENT_NAME); 168 synchronized (mSystemClients) { 169 terminate(mSystemClients); 170 } 171 synchronized (mCurrentUserClients) { 172 terminate(mCurrentUserClients); 173 } 174 } 175 176 @Override dump(PrintWriter writer)177 public void dump(PrintWriter writer) { 178 dumpMetrics(writer); 179 } 180 181 @Override dumpMetrics(PrintWriter writer)182 public void dumpMetrics(PrintWriter writer) { 183 writer.println("*" + getClass().getSimpleName() + "*"); 184 synchronized (mSystemClients) { 185 writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected")); 186 writer.println("mSystemClients:"); 187 dumpConnections(writer, mSystemClients); 188 } 189 synchronized (mCurrentUserClients) { 190 writer.println("mCurrentUserClients:"); 191 dumpConnections(writer, mCurrentUserClients); 192 writer.println("mCurrentUser:" + mCurrentUser); 193 } 194 synchronized (mRebindCounts) { 195 writer.println("mRebindCounts:"); 196 for (Map.Entry<String, AtomicLong> entry : mRebindCounts.entrySet()) { 197 writer.printf("\t%s: %s\n", entry.getKey(), entry.getValue()); 198 } 199 } 200 } 201 dumpConnections(PrintWriter writer, Map<String, ClientConnection> connectionMap)202 private void dumpConnections(PrintWriter writer, Map<String, ClientConnection> connectionMap) { 203 for (ClientConnection connection : connectionMap.values()) { 204 writer.printf("\t%s: %s\n", 205 connection.mName.getPackageName(), 206 connection.mIsBound ? "connected" : "disconnected"); 207 } 208 } 209 210 /** 211 * Registers a new client connection state listener. 212 * 213 * @param listener Listener to register. 214 */ registerConnectionListener(ConnectionListener listener)215 public void registerConnectionListener(ConnectionListener listener) { 216 synchronized (mListeners) { 217 if (!mListeners.contains(listener)) { 218 mListeners.add(listener); 219 } 220 } 221 notifyListenerOfConnectedClients(listener); 222 } 223 224 /** 225 * Unregisters a client connection state listener. 226 * 227 * @param listener Listener to remove. 228 */ unregisterConnectionListener(ConnectionListener listener)229 public void unregisterConnectionListener(ConnectionListener listener) { 230 synchronized (mListeners) { 231 mListeners.remove(listener); 232 } 233 } 234 bindToSystemClients()235 private void bindToSystemClients() { 236 String[] clientNames = mContext.getResources().getStringArray( 237 R.array.vmsPublisherSystemClients); 238 Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)"); 239 synchronized (mSystemClients) { 240 if (!mSystemUserUnlocked) { 241 return; 242 } 243 for (String clientName : clientNames) { 244 bind(mSystemClients, clientName, UserHandle.SYSTEM); 245 } 246 } 247 } 248 bindToUserClients()249 private void bindToUserClients() { 250 synchronized (mCurrentUserClients) { 251 // To avoid the risk of double-binding, clients running as the system user must only 252 // ever be bound in bindToSystemClients(). 253 // In a headless multi-user system, the system user will never be in the foreground. 254 if (mCurrentUser == UserHandle.USER_SYSTEM) { 255 Log.e(TAG, "System user in foreground. Userspace clients will not be bound."); 256 return; 257 } 258 259 String[] clientNames = mContext.getResources().getStringArray( 260 R.array.vmsPublisherUserClients); 261 Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)"); 262 UserHandle currentUserHandle = UserHandle.of(mCurrentUser); 263 for (String clientName : clientNames) { 264 bind(mCurrentUserClients, clientName, currentUserHandle); 265 } 266 } 267 } 268 bind(Map<String, ClientConnection> connectionMap, String clientName, UserHandle userHandle)269 private void bind(Map<String, ClientConnection> connectionMap, String clientName, 270 UserHandle userHandle) { 271 if (connectionMap.containsKey(clientName)) { 272 Log.i(TAG, "Already bound: " + clientName); 273 return; 274 } 275 276 ComponentName name = ComponentName.unflattenFromString(clientName); 277 if (name == null) { 278 Log.e(TAG, "Invalid client name: " + clientName); 279 return; 280 } 281 282 ServiceInfo serviceInfo; 283 try { 284 serviceInfo = mContext.getPackageManager().getServiceInfo(name, 285 PackageManager.MATCH_DIRECT_BOOT_AUTO); 286 } catch (PackageManager.NameNotFoundException e) { 287 Log.w(TAG, "Client not installed: " + clientName); 288 return; 289 } 290 291 if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) { 292 Log.w(TAG, "Client service: " + clientName 293 + " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission"); 294 return; 295 } 296 297 ClientConnection connection = new ClientConnection(name, userHandle); 298 if (connection.bind()) { 299 Log.i(TAG, "Client bound: " + connection); 300 connectionMap.put(clientName, connection); 301 } else { 302 Log.w(TAG, "Binding failed: " + connection); 303 } 304 } 305 terminate(Map<String, ClientConnection> connectionMap)306 private void terminate(Map<String, ClientConnection> connectionMap) { 307 connectionMap.values().forEach(ClientConnection::terminate); 308 connectionMap.clear(); 309 } 310 notifyListenerOfConnectedClients(ConnectionListener listener)311 private void notifyListenerOfConnectedClients(ConnectionListener listener) { 312 synchronized (mSystemClients) { 313 if (mHalClient != null) { 314 listener.onClientConnected(HAL_CLIENT_NAME, mHalClient); 315 } 316 mSystemClients.values().forEach(conn -> conn.notifyIfConnected(listener)); 317 } 318 synchronized (mCurrentUserClients) { 319 mCurrentUserClients.values().forEach(conn -> conn.notifyIfConnected(listener)); 320 } 321 } 322 notifyListenersOnClientConnected(String clientName, IBinder clientService)323 private void notifyListenersOnClientConnected(String clientName, IBinder clientService) { 324 synchronized (mListeners) { 325 for (ConnectionListener listener : mListeners) { 326 listener.onClientConnected(clientName, clientService); 327 } 328 } 329 } 330 notifyListenersOnClientDisconnected(String clientName)331 private void notifyListenersOnClientDisconnected(String clientName) { 332 synchronized (mListeners) { 333 for (ConnectionListener listener : mListeners) { 334 listener.onClientDisconnected(clientName); 335 } 336 } 337 } 338 onHalConnected(IBinder halClient)339 private void onHalConnected(IBinder halClient) { 340 synchronized (mSystemClients) { 341 mHalClient = halClient; 342 notifyListenersOnClientConnected(HAL_CLIENT_NAME, mHalClient); 343 } 344 } 345 onHalDisconnected()346 private void onHalDisconnected() { 347 synchronized (mSystemClients) { 348 mHalClient = null; 349 notifyListenersOnClientDisconnected(HAL_CLIENT_NAME); 350 } 351 synchronized (mRebindCounts) { 352 mRebindCounts.computeIfAbsent(HAL_CLIENT_NAME, k -> new AtomicLong()).incrementAndGet(); 353 } 354 } 355 356 class ClientConnection implements ServiceConnection { 357 private final ComponentName mName; 358 private final UserHandle mUser; 359 private final String mFullName; 360 private boolean mIsBound = false; 361 private boolean mIsTerminated = false; 362 private IBinder mClientService; 363 ClientConnection(ComponentName name, UserHandle user)364 ClientConnection(ComponentName name, UserHandle user) { 365 mName = name; 366 mUser = user; 367 mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier(); 368 } 369 bind()370 synchronized boolean bind() { 371 if (mIsBound) { 372 return true; 373 } 374 if (mIsTerminated) { 375 return false; 376 } 377 378 if (DBG) Log.d(TAG, "binding: " + mFullName); 379 Intent intent = new Intent(); 380 intent.setComponent(mName); 381 try { 382 mIsBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, 383 mHandler, mUser); 384 } catch (SecurityException e) { 385 Log.e(TAG, "While binding " + mFullName, e); 386 } 387 388 return mIsBound; 389 } 390 unbind()391 synchronized void unbind() { 392 if (!mIsBound) { 393 return; 394 } 395 396 if (DBG) Log.d(TAG, "unbinding: " + mFullName); 397 try { 398 mContext.unbindService(this); 399 } catch (Throwable t) { 400 Log.e(TAG, "While unbinding " + mFullName, t); 401 } 402 mIsBound = false; 403 if (mClientService != null) { 404 notifyListenersOnClientDisconnected(mFullName); 405 } 406 mClientService = null; 407 } 408 rebind()409 synchronized void rebind() { 410 unbind(); 411 if (DBG) { 412 Log.d(TAG, 413 String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind)); 414 } 415 if (!mIsTerminated) { 416 mHandler.postDelayed(this::bind, mMillisBeforeRebind); 417 synchronized (mRebindCounts) { 418 mRebindCounts.computeIfAbsent(mName.getPackageName(), k -> new AtomicLong()) 419 .incrementAndGet(); 420 } 421 } 422 } 423 terminate()424 synchronized void terminate() { 425 if (DBG) Log.d(TAG, "terminating: " + mFullName); 426 mIsTerminated = true; 427 unbind(); 428 } 429 notifyIfConnected(ConnectionListener listener)430 synchronized void notifyIfConnected(ConnectionListener listener) { 431 if (mClientService != null) { 432 listener.onClientConnected(mFullName, mClientService); 433 } 434 } 435 436 @Override onServiceConnected(ComponentName name, IBinder service)437 public void onServiceConnected(ComponentName name, IBinder service) { 438 if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName); 439 mClientService = service; 440 notifyListenersOnClientConnected(mFullName, mClientService); 441 } 442 443 @Override onServiceDisconnected(ComponentName name)444 public void onServiceDisconnected(ComponentName name) { 445 if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName); 446 rebind(); 447 } 448 449 @Override toString()450 public String toString() { 451 return mFullName; 452 } 453 } 454 } 455