1 /* 2 * Copyright (C) 2015 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 android.car; 18 19 import android.annotation.IntDef; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.car.annotation.FutureFeature; 23 import android.car.cluster.CarInstrumentClusterManager; 24 import android.car.content.pm.CarPackageManager; 25 import android.car.diagnostic.CarDiagnosticManager; 26 import android.car.hardware.CarSensorManager; 27 import android.car.hardware.CarVendorExtensionManager; 28 import android.car.hardware.cabin.CarCabinManager; 29 import android.car.hardware.hvac.CarHvacManager; 30 import android.car.hardware.radio.CarRadioManager; 31 import android.car.media.CarAudioManager; 32 import android.car.navigation.CarNavigationStatusManager; 33 import android.car.CarBluetoothManager; 34 import android.car.test.CarTestManagerBinderWrapper; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.ServiceConnection; 39 import android.content.pm.PackageManager; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.RemoteException; 44 import android.os.UserHandle; 45 import android.util.Log; 46 47 import com.android.car.internal.FeatureConfiguration; 48 import com.android.internal.annotations.GuardedBy; 49 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.util.HashMap; 53 54 /** 55 * Top level car API for embedded Android Auto deployments. 56 * This API works only for devices with {@link PackageManager#FEATURE_AUTOMOTIVE} 57 * Calling this API on a device with no such feature will lead to an exception. 58 */ 59 public final class Car { 60 61 /** 62 * Represent the version of Car API. This is only updated when there is API change. 63 * 1 : N 64 * 2 : O 65 * 3 : O-MR1 66 */ 67 public static final int VERSION = 3; 68 69 /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */ 70 public static final String SENSOR_SERVICE = "sensor"; 71 72 /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */ 73 public static final String INFO_SERVICE = "info"; 74 75 /** Service name for {@link CarAppFocusManager}. */ 76 public static final String APP_FOCUS_SERVICE = "app_focus"; 77 78 /** Service name for {@link CarPackageManager} */ 79 public static final String PACKAGE_SERVICE = "package"; 80 81 /** Service name for {@link CarAudioManager} */ 82 public static final String AUDIO_SERVICE = "audio"; 83 84 /** 85 * Service name for {@link CarNavigationStatusManager} 86 * @hide 87 */ 88 public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service"; 89 /** 90 * Service name for {@link CarInstrumentClusterManager} 91 * @hide 92 */ 93 public static final String CAR_INSTRUMENT_CLUSTER_SERVICE = "cluster_service"; 94 95 /** 96 * @hide 97 */ 98 @SystemApi 99 public static final String CABIN_SERVICE = "cabin"; 100 101 /** 102 * @hide 103 */ 104 @SystemApi 105 public static final String DIAGNOSTIC_SERVICE = "diagnostic"; 106 107 /** 108 * @hide 109 */ 110 @SystemApi 111 public static final String RADIO_SERVICE = "radio"; 112 113 /** 114 * @hide 115 */ 116 @SystemApi 117 public static final String HVAC_SERVICE = "hvac"; 118 119 /** 120 * @hide 121 */ 122 @SystemApi 123 public static final String PROJECTION_SERVICE = "projection"; 124 125 /** 126 * @hide 127 */ 128 @SystemApi 129 public static final String VENDOR_EXTENSION_SERVICE = "vendor_extension"; 130 131 /** 132 * @hide 133 */ 134 public static final String BLUETOOTH_SERVICE = "car_bluetooth"; 135 136 /** 137 * Service for testing. This is system app only feature. 138 * Service name for {@link CarTestManager}, to be used in {@link #getCarManager(String)}. 139 * @hide 140 */ 141 @SystemApi 142 public static final String TEST_SERVICE = "car-service-test"; 143 144 /** Permission necessary to access car's mileage information. */ 145 public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE"; 146 147 /** Permission necessary to access car's fuel level. */ 148 public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL"; 149 150 /** Permission necessary to access car's speed. */ 151 public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; 152 153 /** Permission necessary to access car's dynamics state. */ 154 public static final String PERMISSION_VEHICLE_DYNAMICS_STATE = 155 "android.car.permission.VEHICLE_DYNAMICS_STATE"; 156 157 /** 158 * Permission necessary to change car audio volume through {@link CarAudioManager}. 159 */ 160 public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = 161 "android.car.permission.CAR_CONTROL_AUDIO_VOLUME"; 162 163 /** 164 * Permission necessary to change car audio settings through {@link CarAudioManager}. 165 * @hide 166 */ 167 public static final String PERMISSION_CAR_CONTROL_AUDIO_SETTINGS = 168 "android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"; 169 170 /** 171 * Permission necessary to use {@link CarNavigationStatusManager}. 172 * @hide 173 */ 174 public static final String PERMISSION_CAR_NAVIGATION_MANAGER = 175 "android.car.permission.CAR_NAVIGATION_MANAGER"; 176 177 /** 178 * Permission necessary to start activities in the instrument cluster through 179 * {@link CarInstrumentClusterManager} 180 * 181 * @hide 182 */ 183 public static final String PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL = 184 "android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"; 185 186 /** 187 * Application must have this permission in order to be launched in the instrument cluster 188 * display. 189 * 190 * @hide 191 */ 192 public static final String PERMISSION_CAR_DISPLAY_IN_CLUSTER = 193 "android.car.permission.CAR_DISPLAY_IN_CLUSTER"; 194 195 /** 196 * Permission necessary to access car specific communication channel. 197 * @hide 198 */ 199 @SystemApi 200 public static final String PERMISSION_VENDOR_EXTENSION = 201 "android.car.permission.CAR_VENDOR_EXTENSION"; 202 203 /** 204 * @hide 205 */ 206 @SystemApi 207 public static final String PERMISSION_CONTROL_APP_BLOCKING = 208 "android.car.permission.CONTROL_APP_BLOCKING"; 209 210 /** 211 * Permission necessary to access Car Cabin APIs. 212 * @hide 213 */ 214 @SystemApi 215 public static final String PERMISSION_CAR_CABIN = "android.car.permission.CAR_CABIN"; 216 217 /** 218 * Permission necessary to access Car HVAC APIs. 219 * @hide 220 */ 221 @SystemApi 222 public static final String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC"; 223 224 /** 225 * Permission necessary to access Car RADIO system APIs. 226 * @hide 227 */ 228 @SystemApi 229 public static final String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO"; 230 231 232 /** 233 * Permission necessary to access Car PROJECTION system APIs. 234 * @hide 235 */ 236 @SystemApi 237 public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION"; 238 239 /** 240 * Permission necessary to mock vehicle hal for testing. 241 * @hide 242 * @deprecated mocking vehicle HAL in car service is no longer supported. 243 */ 244 @SystemApi 245 public static final String PERMISSION_MOCK_VEHICLE_HAL = 246 "android.car.permission.CAR_MOCK_VEHICLE_HAL"; 247 248 /** 249 * Permission necessary to access CarTestService. 250 * @hide 251 */ 252 @SystemApi 253 public static final String PERMISSION_CAR_TEST_SERVICE = 254 "android.car.permission.CAR_TEST_SERVICE"; 255 256 /** 257 * Permissions necessary to read diagnostic information, including vendor-specific bits. 258 * 259 * @hide 260 */ 261 @SystemApi 262 public static final String PERMISSION_CAR_DIAGNOSTIC_READ_ALL = 263 "android.car.permission.DIAGNOSTIC_READ_ALL"; 264 265 /** 266 * Permissions necessary to clear diagnostic information. 267 * 268 * @hide 269 */ 270 @SystemApi 271 public static final String PERMISSION_CAR_DIAGNOSTIC_CLEAR = "android.car.permission.DIAGNOSTIC_CLEAR"; 272 273 /** Type of car connection: platform runs directly in car. */ 274 public static final int CONNECTION_TYPE_EMBEDDED = 5; 275 276 277 /** @hide */ 278 @IntDef({CONNECTION_TYPE_EMBEDDED}) 279 @Retention(RetentionPolicy.SOURCE) 280 public @interface ConnectionType {} 281 282 /** 283 * CarXyzService throws IllegalStateException with this message is re-thrown as 284 * {@link CarNotConnectedException}. 285 * 286 * @hide 287 */ 288 public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected"; 289 290 /** 291 * Activity Action: Provide media playing through a media template app. 292 * <p>Input: String extra mapped by {@link android.app.SearchManager#QUERY} is the query 293 * used to start the media. String extra mapped by {@link #CAR_EXTRA_MEDIA_PACKAGE} is the 294 * package name of the media app which user wants to play media on. 295 * <p>Output: nothing. 296 */ 297 public static final String CAR_INTENT_ACTION_MEDIA_TEMPLATE = 298 "android.car.intent.action.MEDIA_TEMPLATE"; 299 300 /** 301 * Used as a string extra field with {@link #CAR_INTENT_ACTION_MEDIA_TEMPLATE} to specify the 302 * media app that user wants to start the media on. Note: this is not the templated media app. 303 */ 304 public static final String CAR_EXTRA_MEDIA_PACKAGE = "android.car.intent.extra.MEDIA_PACKAGE"; 305 306 /** @hide */ 307 public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar"; 308 309 private static final String CAR_SERVICE_PACKAGE = "com.android.car"; 310 311 private static final String CAR_SERVICE_CLASS = "com.android.car.CarService"; 312 313 private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500; 314 private static final long CAR_SERVICE_BIND_MAX_RETRY = 20; 315 316 private final Context mContext; 317 @GuardedBy("this") 318 private ICar mService; 319 private final boolean mOwnsService; 320 private static final int STATE_DISCONNECTED = 0; 321 private static final int STATE_CONNECTING = 1; 322 private static final int STATE_CONNECTED = 2; 323 @GuardedBy("this") 324 private int mConnectionState; 325 @GuardedBy("this") 326 private int mConnectionRetryCount; 327 328 private final Runnable mConnectionRetryRunnable = new Runnable() { 329 @Override 330 public void run() { 331 startCarService(); 332 } 333 }; 334 335 private final Runnable mConnectionRetryFailedRunnable = new Runnable() { 336 @Override 337 public void run() { 338 mServiceConnectionListener.onServiceDisconnected(new ComponentName(CAR_SERVICE_PACKAGE, 339 CAR_SERVICE_CLASS)); 340 } 341 }; 342 343 private final ServiceConnection mServiceConnectionListener = 344 new ServiceConnection () { 345 public void onServiceConnected(ComponentName name, IBinder service) { 346 synchronized (Car.this) { 347 mService = ICar.Stub.asInterface(service); 348 mConnectionState = STATE_CONNECTED; 349 } 350 mServiceConnectionListenerClient.onServiceConnected(name, service); 351 } 352 353 public void onServiceDisconnected(ComponentName name) { 354 synchronized (Car.this) { 355 mService = null; 356 if (mConnectionState == STATE_DISCONNECTED) { 357 return; 358 } 359 mConnectionState = STATE_DISCONNECTED; 360 } 361 // unbind explicitly here. 362 disconnect(); 363 mServiceConnectionListenerClient.onServiceDisconnected(name); 364 } 365 }; 366 367 private final ServiceConnection mServiceConnectionListenerClient; 368 private final Object mCarManagerLock = new Object(); 369 @GuardedBy("mCarManagerLock") 370 private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>(); 371 372 /** Handler for generic event dispatching. */ 373 private final Handler mEventHandler; 374 375 private final Handler mMainThreadEventHandler; 376 377 /** 378 * A factory method that creates Car instance for all Car API access. 379 * @param context 380 * @param serviceConnectionListener listener for monitoring service connection. 381 * @param handler the handler on which the callback should execute, or null to execute on the 382 * service's main thread. Note: the service connection listener will be always on the main 383 * thread regardless of the handler given. 384 * @return Car instance if system is in car environment and returns {@code null} otherwise. 385 */ createCar(Context context, ServiceConnection serviceConnectionListener, @Nullable Handler handler)386 public static Car createCar(Context context, ServiceConnection serviceConnectionListener, 387 @Nullable Handler handler) { 388 if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 389 Log.e(CarLibLog.TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used"); 390 return null; 391 } 392 try { 393 return new Car(context, serviceConnectionListener, handler); 394 } catch (IllegalArgumentException e) { 395 // Expected when car service loader is not available. 396 } 397 return null; 398 } 399 400 /** 401 * A factory method that creates Car instance for all Car API access using main thread {@code 402 * Looper}. 403 * 404 * @see #createCar(Context, ServiceConnection, Handler) 405 */ createCar(Context context, ServiceConnection serviceConnectionListener)406 public static Car createCar(Context context, ServiceConnection serviceConnectionListener) { 407 return createCar(context, serviceConnectionListener, null); 408 } 409 Car(Context context, ServiceConnection serviceConnectionListener, @Nullable Handler handler)410 private Car(Context context, ServiceConnection serviceConnectionListener, 411 @Nullable Handler handler) { 412 mContext = context; 413 mEventHandler = determineEventHandler(handler); 414 mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler); 415 416 mService = null; 417 mOwnsService = true; 418 mServiceConnectionListenerClient = serviceConnectionListener; 419 } 420 421 422 /** 423 * Car constructor when ICar binder is already available. 424 * @hide 425 */ Car(Context context, ICar service, @Nullable Handler handler)426 public Car(Context context, ICar service, @Nullable Handler handler) { 427 mContext = context; 428 mEventHandler = determineEventHandler(handler); 429 mMainThreadEventHandler = determineMainThreadEventHandler(mEventHandler); 430 431 mService = service; 432 mOwnsService = false; 433 mConnectionState = STATE_CONNECTED; 434 mServiceConnectionListenerClient = null; 435 } 436 determineMainThreadEventHandler(Handler eventHandler)437 private static Handler determineMainThreadEventHandler(Handler eventHandler) { 438 Looper mainLooper = Looper.getMainLooper(); 439 return (eventHandler.getLooper() == mainLooper) ? eventHandler : new Handler(mainLooper); 440 } 441 determineEventHandler(@ullable Handler handler)442 private static Handler determineEventHandler(@Nullable Handler handler) { 443 if (handler == null) { 444 Looper looper = Looper.getMainLooper(); 445 handler = new Handler(looper); 446 } 447 return handler; 448 } 449 450 /** 451 * Connect to car service. This can be called while it is disconnected. 452 * @throws IllegalStateException If connection is still on-going from previous 453 * connect call or it is already connected 454 */ connect()455 public void connect() throws IllegalStateException { 456 synchronized (this) { 457 if (mConnectionState != STATE_DISCONNECTED) { 458 throw new IllegalStateException("already connected or connecting"); 459 } 460 mConnectionState = STATE_CONNECTING; 461 startCarService(); 462 } 463 } 464 465 /** 466 * Disconnect from car service. This can be called while disconnected. Once disconnect is 467 * called, all Car*Managers from this instance becomes invalid, and 468 * {@link Car#getCarManager(String)} will return different instance if it is connected again. 469 */ disconnect()470 public void disconnect() { 471 synchronized (this) { 472 if (mConnectionState == STATE_DISCONNECTED) { 473 return; 474 } 475 mEventHandler.removeCallbacks(mConnectionRetryRunnable); 476 mMainThreadEventHandler.removeCallbacks(mConnectionRetryFailedRunnable); 477 mConnectionRetryCount = 0; 478 tearDownCarManagers(); 479 mService = null; 480 mConnectionState = STATE_DISCONNECTED; 481 482 if (mOwnsService) { 483 mContext.unbindService(mServiceConnectionListener); 484 } 485 } 486 } 487 488 /** 489 * Tells if it is connected to the service or not. This will return false if it is still 490 * connecting. 491 * @return 492 */ isConnected()493 public boolean isConnected() { 494 synchronized (this) { 495 return mService != null; 496 } 497 } 498 499 /** 500 * Tells if this instance is already connecting to car service or not. 501 * @return 502 */ isConnecting()503 public boolean isConnecting() { 504 synchronized (this) { 505 return mConnectionState == STATE_CONNECTING; 506 } 507 } 508 509 /** 510 * Get car specific service as in {@link Context#getSystemService(String)}. Returned 511 * {@link Object} should be type-casted to the desired service. 512 * For example, to get sensor service, 513 * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE); 514 * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}. 515 * @return Matching service manager or null if there is no such service. 516 * @throws CarNotConnectedException if the connection to the car service has been lost. 517 */ getCarManager(String serviceName)518 public Object getCarManager(String serviceName) throws CarNotConnectedException { 519 CarManagerBase manager; 520 ICar service = getICarOrThrow(); 521 synchronized (mCarManagerLock) { 522 manager = mServiceMap.get(serviceName); 523 if (manager == null) { 524 try { 525 IBinder binder = service.getCarService(serviceName); 526 if (binder == null) { 527 Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" + 528 serviceName); 529 return null; 530 } 531 manager = createCarManager(serviceName, binder); 532 if (manager == null) { 533 Log.w(CarLibLog.TAG_CAR, 534 "getCarManager could not create manager for service:" + 535 serviceName); 536 return null; 537 } 538 mServiceMap.put(serviceName, manager); 539 } catch (RemoteException e) { 540 handleRemoteException(e); 541 } 542 } 543 } 544 return manager; 545 } 546 547 /** 548 * Return the type of currently connected car. 549 * @return 550 */ 551 @ConnectionType getCarConnectionType()552 public int getCarConnectionType() { 553 return CONNECTION_TYPE_EMBEDDED; 554 } 555 556 /** 557 * IllegalStateException from XyzCarService with special message is re-thrown as a different 558 * exception. If the IllegalStateException is not understood then this message will throw the 559 * original exception. 560 * 561 * @param e exception from XyzCarService. 562 * @throws CarNotConnectedException if the connection to the car service has been lost. 563 * @hide 564 */ checkCarNotConnectedExceptionFromCarService( IllegalStateException e)565 public static void checkCarNotConnectedExceptionFromCarService( 566 IllegalStateException e) throws CarNotConnectedException, IllegalStateException { 567 String message = e.getMessage(); 568 if (CAR_NOT_CONNECTED_EXCEPTION_MSG.equals(message)) { 569 throw new CarNotConnectedException(); 570 } else { 571 throw e; 572 } 573 } 574 575 /** @hide */ hideCarNotConnectedExceptionFromCarService( IllegalStateException e)576 public static void hideCarNotConnectedExceptionFromCarService( 577 IllegalStateException e) throws IllegalStateException { 578 String message = e.getMessage(); 579 if (CAR_NOT_CONNECTED_EXCEPTION_MSG.equals(message)) { 580 return; //ignore 581 } else { 582 throw e; 583 } 584 } 585 createCarManager(String serviceName, IBinder binder)586 private CarManagerBase createCarManager(String serviceName, IBinder binder) 587 throws CarNotConnectedException { 588 CarManagerBase manager = null; 589 switch (serviceName) { 590 case AUDIO_SERVICE: 591 manager = new CarAudioManager(binder, mContext, mEventHandler); 592 break; 593 case SENSOR_SERVICE: 594 manager = new CarSensorManager(binder, mContext, mEventHandler); 595 break; 596 case INFO_SERVICE: 597 manager = new CarInfoManager(binder); 598 break; 599 case APP_FOCUS_SERVICE: 600 manager = new CarAppFocusManager(binder, mEventHandler); 601 break; 602 case PACKAGE_SERVICE: 603 manager = new CarPackageManager(binder, mContext); 604 break; 605 case CAR_NAVIGATION_SERVICE: 606 manager = new CarNavigationStatusManager(binder); 607 break; 608 case CABIN_SERVICE: 609 manager = new CarCabinManager(binder, mContext, mEventHandler); 610 break; 611 case DIAGNOSTIC_SERVICE: 612 manager = new CarDiagnosticManager(binder, mContext, mEventHandler); 613 break; 614 case HVAC_SERVICE: 615 manager = new CarHvacManager(binder, mContext, mEventHandler); 616 break; 617 case PROJECTION_SERVICE: 618 manager = new CarProjectionManager(binder, mEventHandler); 619 break; 620 case RADIO_SERVICE: 621 manager = new CarRadioManager(binder, mEventHandler); 622 break; 623 case VENDOR_EXTENSION_SERVICE: 624 manager = new CarVendorExtensionManager(binder, mEventHandler); 625 break; 626 case CAR_INSTRUMENT_CLUSTER_SERVICE: 627 manager = new CarInstrumentClusterManager(binder, mEventHandler); 628 break; 629 case TEST_SERVICE: 630 /* CarTestManager exist in static library. So instead of constructing it here, 631 * only pass binder wrapper so that CarTestManager can be constructed outside. */ 632 manager = new CarTestManagerBinderWrapper(binder); 633 break; 634 case BLUETOOTH_SERVICE: 635 manager = new CarBluetoothManager(binder, mContext); 636 } 637 return manager; 638 } 639 startCarService()640 private void startCarService() { 641 Intent intent = new Intent(); 642 intent.setPackage(CAR_SERVICE_PACKAGE); 643 intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME); 644 boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener, 645 Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF); 646 if (!bound) { 647 mConnectionRetryCount++; 648 if (mConnectionRetryCount > CAR_SERVICE_BIND_MAX_RETRY) { 649 Log.w(CarLibLog.TAG_CAR, "cannot bind to car service after max retry"); 650 mMainThreadEventHandler.post(mConnectionRetryFailedRunnable); 651 } else { 652 mEventHandler.postDelayed(mConnectionRetryRunnable, 653 CAR_SERVICE_BIND_RETRY_INTERVAL_MS); 654 } 655 } else { 656 mConnectionRetryCount = 0; 657 } 658 } 659 getICarOrThrow()660 private synchronized ICar getICarOrThrow() throws IllegalStateException { 661 if (mService == null) { 662 throw new IllegalStateException("not connected"); 663 } 664 return mService; 665 } 666 handleRemoteException(RemoteException e)667 private void handleRemoteException(RemoteException e) { 668 Log.w(CarLibLog.TAG_CAR, "RemoteException", e); 669 disconnect(); 670 } 671 tearDownCarManagers()672 private void tearDownCarManagers() { 673 synchronized (mCarManagerLock) { 674 for (CarManagerBase manager: mServiceMap.values()) { 675 manager.onCarDisconnected(); 676 } 677 mServiceMap.clear(); 678 } 679 } 680 } 681