Car.java revision 0477e29bb17ee8ec99acfa5fa966889cd45ebf34
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 17package android.car; 18 19import android.annotation.IntDef; 20import android.annotation.Nullable; 21import android.annotation.SystemApi; 22import android.car.content.pm.CarPackageManager; 23import android.car.hardware.CarSensorManager; 24import android.car.hardware.camera.CarCameraManager; 25import android.car.hardware.hvac.CarHvacManager; 26import android.car.hardware.radio.CarRadioManager; 27import android.car.media.CarAudioManager; 28import android.car.navigation.CarNavigationManager; 29import android.car.test.CarTestManagerBinderWrapper; 30import android.content.ComponentName; 31import android.content.Context; 32import android.content.Intent; 33import android.content.ServiceConnection; 34import android.content.pm.PackageManager; 35import android.os.Handler; 36import android.os.IBinder; 37import android.os.Looper; 38import android.os.RemoteException; 39import android.os.UserHandle; 40import android.util.Log; 41 42import com.android.internal.annotations.GuardedBy; 43 44import java.lang.annotation.Retention; 45import java.lang.annotation.RetentionPolicy; 46import java.util.HashMap; 47 48/** 49 * Top level car API for embedded Android Auto deployments. 50 * This API works only for devices with {@link PackageManager#FEATURE_AUTOMOTIVE} 51 * Calling this API on a device with no such feature will lead to an exception. 52 */ 53public class Car { 54 55 /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */ 56 public static final String SENSOR_SERVICE = "sensor"; 57 58 /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */ 59 public static final String INFO_SERVICE = "info"; 60 61 /** Service name for {@link CarAppContextManager}. */ 62 public static final String APP_FOCUS_SERVICE = "app_focus"; 63 64 /** Service name for {@link CarPackageManager} */ 65 public static final String PACKAGE_SERVICE = "package"; 66 67 /** Service name for {@link CarAudioManager} */ 68 public static final String AUDIO_SERVICE = "audio"; 69 /** 70 * Service name for {@link CarNavigationManager} 71 * @hide 72 */ 73 public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service"; 74 75 /** 76 * @hide 77 */ 78 @SystemApi 79 public static final String CAMERA_SERVICE = "camera"; 80 81 /** 82 * @hide 83 */ 84 @SystemApi 85 public static final String RADIO_SERVICE = "radio"; 86 87 /** 88 * @hide 89 */ 90 @SystemApi 91 public static final String HVAC_SERVICE = "hvac"; 92 93 /** 94 * @hide 95 */ 96 @SystemApi 97 public static final String PROJECTION_SERVICE = "projection"; 98 99 /** 100 * Service for testing. This is system app only feature. 101 * Service name for {@link CarTestManager}, to be used in {@link #getCarManager(String)}. 102 * @hide 103 */ 104 @SystemApi 105 public static final String TEST_SERVICE = "car-service-test"; 106 107 /** Permission necessary to access car's mileage information. */ 108 public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE"; 109 110 /** Permission necessary to access car's fuel level. */ 111 public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL"; 112 113 /** Permission necessary to access car's speed. */ 114 public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; 115 116 /** 117 * Permission necessary to change car audio volume through {@link CarAudioManager}. 118 */ 119 public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = 120 "android.car.permission.CAR_CONTROL_AUDIO_VOLUME"; 121 122 /** 123 * Permission necessary to use {@link CarNavigationManager}. 124 * @hide 125 */ 126 public static final String PERMISSION_CAR_NAVIGATION_MANAGER = 127 "android.car.permission.CAR_NAVIGATION_MANAGER"; 128 129 /** 130 * Permission necessary to access car specific communication channel. 131 * @hide 132 */ 133 @SystemApi 134 public static final String PERMISSION_VENDOR_EXTENSION = 135 "android.car.permission.CAR_VENDOR_EXTENSION"; 136 137 /** 138 * @hide 139 */ 140 @SystemApi 141 public static final String PERMISSION_CONTROL_APP_BLOCKING = 142 "android.car.permission.CONTROL_APP_BLOCKING"; 143 144 /** 145 * Permission necessary to access Car Camera APIs. 146 * @hide 147 */ 148 @SystemApi 149 public static final String PERMISSION_CAR_CAMERA = "android.car.permission.CAR_CAMERA"; 150 151 /** 152 * Permission necessary to access Car HVAC APIs. 153 * @hide 154 */ 155 @SystemApi 156 public static final String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC"; 157 158 /** 159 * Permission necessary to access Car RADIO system APIs. 160 * @hide 161 */ 162 @SystemApi 163 public static final String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO"; 164 165 166 /** 167 * Permission necessary to access Car PROJECTION system APIs. 168 * @hide 169 */ 170 @SystemApi 171 public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION"; 172 173 /** 174 * Permission necessary to mock vehicle hal for testing. 175 * @hide 176 */ 177 @SystemApi 178 public static final String PERMISSION_MOCK_VEHICLE_HAL = 179 "android.car.permission.CAR_MOCK_VEHICLE_HAL"; 180 181 /** Type of car connection: platform runs directly in car. */ 182 public static final int CONNECTION_TYPE_EMBEDDED = 5; 183 /** 184 * Type of car connection: platform runs directly in car but with mocked vehicle hal. 185 * This will only happen in testing environment. 186 * @hide 187 */ 188 public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6; 189 190 191 /** @hide */ 192 @IntDef({CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_EMBEDDED_MOCKING}) 193 @Retention(RetentionPolicy.SOURCE) 194 public @interface ConnectionType {} 195 196 /** 197 * CarXyzService throws IllegalStateException with this message is re-thrown as 198 * {@link CarNotConnectedException}. 199 * 200 * @hide 201 */ 202 public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected"; 203 204 /** @hide */ 205 public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar"; 206 207 private static final String CAR_SERVICE_PACKAGE = "com.android.car"; 208 209 private static final String CAR_SERVICE_CLASS = "com.android.car.CarService"; 210 211 private static final String CAR_TEST_MANAGER_CLASS = "android.car.CarTestManager"; 212 213 private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500; 214 private static final long CAR_SERVICE_BIND_MAX_RETRY = 20; 215 216 private final Context mContext; 217 private final Looper mLooper; 218 @GuardedBy("this") 219 private ICar mService; 220 private static final int STATE_DISCONNECTED = 0; 221 private static final int STATE_CONNECTING = 1; 222 private static final int STATE_CONNECTED = 2; 223 @GuardedBy("this") 224 private int mConnectionState; 225 @GuardedBy("this") 226 private int mConnectionRetryCount; 227 228 private final Runnable mConnectionRetryRunnable = new Runnable() { 229 @Override 230 public void run() { 231 startCarService(); 232 } 233 }; 234 235 private final Runnable mConnectionRetryFailedRunnable = new Runnable() { 236 @Override 237 public void run() { 238 mServiceConnectionListener.onServiceDisconnected(new ComponentName(CAR_SERVICE_PACKAGE, 239 CAR_SERVICE_CLASS)); 240 } 241 }; 242 243 private final ServiceConnection mServiceConnectionListener = 244 new ServiceConnection () { 245 public void onServiceConnected(ComponentName name, IBinder service) { 246 synchronized (Car.this) { 247 mService = ICar.Stub.asInterface(service); 248 mConnectionState = STATE_CONNECTED; 249 } 250 mServiceConnectionListenerClient.onServiceConnected(name, service); 251 } 252 253 public void onServiceDisconnected(ComponentName name) { 254 synchronized (Car.this) { 255 mService = null; 256 if (mConnectionState == STATE_DISCONNECTED) { 257 return; 258 } 259 mConnectionState = STATE_DISCONNECTED; 260 } 261 // unbind explicitly here. 262 disconnect(); 263 mServiceConnectionListenerClient.onServiceDisconnected(name); 264 } 265 }; 266 267 private final ServiceConnection mServiceConnectionListenerClient; 268 private final Object mCarManagerLock = new Object(); 269 @GuardedBy("mCarManagerLock") 270 private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>(); 271 272 /** Handler for generic event dispatching. */ 273 private final Handler mEventHandler; 274 275 private final Handler mMainThreadEvetHandler; 276 277 /** 278 * A factory method that creates Car instance for all Car API access. 279 * @param context 280 * @param serviceConnectionListener listener for monitoring service connection. 281 * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that 282 * service connection listener will be always in main thread regardless of this Looper. 283 * @return Car instance if system is in car environment and returns {@code null} otherwise. 284 */ 285 public static Car createCar(Context context, ServiceConnection serviceConnectionListener, 286 @Nullable Looper looper) { 287 if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 288 Log.e(CarLibLog.TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used"); 289 return null; 290 } 291 try { 292 return new Car(context, serviceConnectionListener, looper); 293 } catch (IllegalArgumentException e) { 294 // Expected when car service loader is not available. 295 } 296 return null; 297 } 298 299 /** 300 * A factory method that creates Car instance for all Car API access using main thread {@code 301 * Looper}. 302 * 303 * @see #createCar(Context, ServiceConnection, Looper) 304 */ 305 public static Car createCar(Context context, ServiceConnection serviceConnectionListener) { 306 return createCar(context, serviceConnectionListener, null); 307 } 308 309 private Car(Context context, ServiceConnection serviceConnectionListener, 310 @Nullable Looper looper) { 311 mContext = context; 312 mServiceConnectionListenerClient = serviceConnectionListener; 313 if (looper == null) { 314 mLooper = Looper.getMainLooper(); 315 } else { 316 mLooper = looper; 317 } 318 mEventHandler = new Handler(mLooper); 319 if (mLooper == Looper.getMainLooper()) { 320 mMainThreadEvetHandler = mEventHandler; 321 } else { 322 mMainThreadEvetHandler = new Handler(Looper.getMainLooper()); 323 } 324 } 325 326 /** 327 * Car constructor when ICar binder is already available. 328 * @param context 329 * @param service 330 * @param looper 331 * 332 * @hide 333 */ 334 public Car(Context context, ICar service, @Nullable Looper looper) { 335 mContext = context; 336 if (looper == null) { 337 mLooper = Looper.getMainLooper(); 338 } else { 339 mLooper = looper; 340 } 341 mEventHandler = new Handler(mLooper); 342 if (mLooper == Looper.getMainLooper()) { 343 mMainThreadEvetHandler = mEventHandler; 344 } else { 345 mMainThreadEvetHandler = new Handler(Looper.getMainLooper()); 346 } 347 mService = service; 348 mConnectionState = STATE_CONNECTED; 349 mServiceConnectionListenerClient = null; 350 } 351 352 /** 353 * Connect to car service. This can be called while it is disconnected. 354 * @throws IllegalStateException If connection is still on-going from previous 355 * connect call or it is already connected 356 */ 357 public void connect() throws IllegalStateException { 358 synchronized (this) { 359 if (mConnectionState != STATE_DISCONNECTED) { 360 throw new IllegalStateException("already connected or connecting"); 361 } 362 mConnectionState = STATE_CONNECTING; 363 startCarService(); 364 } 365 } 366 367 /** 368 * Disconnect from car service. This can be called while disconnected. Once disconnect is 369 * called, all Car*Managers from this instance becomes invalid, and 370 * {@link Car#getCarManager(String)} will return different instance if it is connected again. 371 */ 372 public void disconnect() { 373 synchronized (this) { 374 if (mConnectionState == STATE_DISCONNECTED) { 375 return; 376 } 377 mEventHandler.removeCallbacks(mConnectionRetryRunnable); 378 mMainThreadEvetHandler.removeCallbacks(mConnectionRetryFailedRunnable); 379 mConnectionRetryCount = 0; 380 tearDownCarManagers(); 381 mService = null; 382 mConnectionState = STATE_DISCONNECTED; 383 mContext.unbindService(mServiceConnectionListener); 384 } 385 } 386 387 /** 388 * Tells if it is connected to the service or not. This will return false if it is still 389 * connecting. 390 * @return 391 */ 392 public boolean isConnected() { 393 synchronized (this) { 394 return mService != null; 395 } 396 } 397 398 /** 399 * Tells if this instance is already connecting to car service or not. 400 * @return 401 */ 402 public boolean isConnecting() { 403 synchronized (this) { 404 return mConnectionState == STATE_CONNECTING; 405 } 406 } 407 408 /** 409 * Get car specific service as in {@link Context#getSystemService(String)}. Returned 410 * {@link Object} should be type-casted to the desired service. 411 * For example, to get sensor service, 412 * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE); 413 * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}. 414 * @return Matching service manager or null if there is no such service. 415 * @throws CarNotConnectedException 416 */ 417 public Object getCarManager(String serviceName) throws CarNotConnectedException { 418 CarManagerBase manager; 419 ICar service = getICarOrThrow(); 420 synchronized (mCarManagerLock) { 421 manager = mServiceMap.get(serviceName); 422 if (manager == null) { 423 try { 424 IBinder binder = service.getCarService(serviceName); 425 if (binder == null) { 426 Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" + 427 serviceName); 428 return null; 429 } 430 manager = createCarManager(serviceName, binder); 431 if (manager == null) { 432 Log.w(CarLibLog.TAG_CAR, 433 "getCarManager could not create manager for service:" + 434 serviceName); 435 return null; 436 } 437 mServiceMap.put(serviceName, manager); 438 } catch (RemoteException e) { 439 handleRemoteException(e); 440 } 441 } 442 } 443 return manager; 444 } 445 446 /** 447 * Return the type of currently connected car. 448 * @return 449 */ 450 @ConnectionType 451 public int getCarConnectionType() { 452 return CONNECTION_TYPE_EMBEDDED; 453 } 454 455 /** 456 * IllegalStateException from XyzCarService with special message is re-thrown as a different 457 * exception. If the IllegalStateException is not understood then this message will throw the 458 * original exception. 459 * 460 * @param e exception from XyzCarService. 461 * @throws CarNotConnectedException 462 * @hide 463 */ 464 public static void checkCarNotConnectedExceptionFromCarService( 465 IllegalStateException e) throws CarNotConnectedException, IllegalStateException { 466 String message = e.getMessage(); 467 if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) { 468 throw new CarNotConnectedException(); 469 } else { 470 throw e; 471 } 472 } 473 474 private CarManagerBase createCarManager(String serviceName, IBinder binder) 475 throws CarNotConnectedException { 476 CarManagerBase manager = null; 477 switch (serviceName) { 478 case AUDIO_SERVICE: 479 manager = new CarAudioManager(binder, mContext); 480 break; 481 case SENSOR_SERVICE: 482 manager = new CarSensorManager(binder, mContext, mLooper); 483 break; 484 case INFO_SERVICE: 485 manager = new CarInfoManager(binder); 486 break; 487 case APP_FOCUS_SERVICE: 488 manager = new CarAppFocusManager(binder, mLooper); 489 break; 490 case PACKAGE_SERVICE: 491 manager = new CarPackageManager(binder, mContext); 492 break; 493 case CAR_NAVIGATION_SERVICE: 494 manager = new CarNavigationManager(binder); 495 break; 496 case CAMERA_SERVICE: 497 manager = new CarCameraManager(binder, mContext); 498 break; 499 case HVAC_SERVICE: 500 manager = new CarHvacManager(binder, mContext, mLooper); 501 break; 502 case PROJECTION_SERVICE: 503 manager = new CarProjectionManager(binder, mLooper); 504 break; 505 case RADIO_SERVICE: 506 manager = new CarRadioManager(binder, mLooper); 507 break; 508 case TEST_SERVICE: 509 /* CarTestManager exist in static library. So instead of constructing it here, 510 * only pass binder wrapper so that CarTestManager can be constructed outside. */ 511 manager = new CarTestManagerBinderWrapper(binder); 512 break; 513 } 514 return manager; 515 } 516 517 private void startCarService() { 518 Intent intent = new Intent(); 519 intent.setPackage(CAR_SERVICE_PACKAGE); 520 intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME); 521 boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener, 522 Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF); 523 if (!bound) { 524 mConnectionRetryCount++; 525 if (mConnectionRetryCount > CAR_SERVICE_BIND_MAX_RETRY) { 526 Log.w(CarLibLog.TAG_CAR, "cannot bind to car service after max retry"); 527 mMainThreadEvetHandler.post(mConnectionRetryFailedRunnable); 528 } else { 529 mEventHandler.postDelayed(mConnectionRetryRunnable, 530 CAR_SERVICE_BIND_RETRY_INTERVAL_MS); 531 } 532 } else { 533 mConnectionRetryCount = 0; 534 } 535 } 536 537 private synchronized ICar getICarOrThrow() throws IllegalStateException { 538 if (mService == null) { 539 throw new IllegalStateException("not connected"); 540 } 541 return mService; 542 } 543 544 private void handleRemoteException(RemoteException e) { 545 Log.w(CarLibLog.TAG_CAR, "RemoteException", e); 546 disconnect(); 547 } 548 549 private void tearDownCarManagers() { 550 synchronized (mCarManagerLock) { 551 for (CarManagerBase manager: mServiceMap.values()) { 552 manager.onCarDisconnected(); 553 } 554 mServiceMap.clear(); 555 } 556 } 557} 558