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