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