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.support.car; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.support.annotation.IntDef; 23 import android.support.annotation.NonNull; 24 import android.support.annotation.Nullable; 25 import android.support.car.content.pm.CarPackageManager; 26 import android.support.car.hardware.CarSensorManager; 27 import android.support.car.media.CarAudioManager; 28 import android.support.car.navigation.CarNavigationStatusManager; 29 import android.util.Log; 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.lang.reflect.Constructor; 33 import java.lang.reflect.InvocationTargetException; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.Map; 38 import java.util.Set; 39 40 /** 41 * Top-level car API that provides access to all car services and data available in the platform. 42 * <p/> 43 * Use one of the createCar methods to create a new instance of the Car api. The 44 * {@link CarConnectionCallback} will respond with an {@link CarConnectionCallback#onConnected(Car)} 45 * or {@link CarConnectionCallback#onDisconnected(Car)} message. Nothing can be done with the 46 * car until onConnected is called. When the car disconnects then reconnects you may still use 47 * the Car object but any manages retrieved from it should be considered invalid and will need to 48 * be retried. Also, you must call {@link #disconnect} before an instance of Car goes out of scope 49 * to avoid leaking resources. 50 * 51 * <p/> 52 * Once connected, {@link #getCarManager(String)} or {@link #getCarManager(Class)} can be used to 53 * retrieve a manager. This is patterned after how one would retrieve a service from 54 * {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)}. Once 55 * again if the car is disconnected you'll want to get new versions of these managers. 56 */ 57 public class Car { 58 59 private static final String TAG = "CAR.SUPPORT.LIB.CAR"; 60 /** 61 * Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. 62 */ 63 public static final String SENSOR_SERVICE = "sensor"; 64 65 /** 66 * Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. 67 */ 68 public static final String INFO_SERVICE = "info"; 69 70 /** 71 * Service name for {@link CarAppFocusManager}. 72 */ 73 public static final String APP_FOCUS_SERVICE = "app_focus"; 74 75 /** 76 * Service name for {@link CarPackageManager}. 77 * @hide 78 */ 79 public static final String PACKAGE_SERVICE = "package"; 80 81 /** 82 * Service name for {@link CarAudioManager}. 83 */ 84 public static final String AUDIO_SERVICE = "audio"; 85 /** 86 * Service name for {@link CarNavigationStatusManager}. 87 * @hide 88 */ 89 public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service"; 90 /** 91 * Service name for {@link CarNavigationStatusManager}. 92 */ 93 public static final String NAVIGATION_STATUS_SERVICE = "car_navigation_service"; 94 95 // TODO(jthol) move into a more robust registry implementation 96 private static final Map<Class, String> CLASS_TO_SERVICE_NAME; 97 static{ 98 Map<Class, String> mapping = new HashMap<>(); 99 mapping.put(CarSensorManager.class, SENSOR_SERVICE); 100 mapping.put(CarInfoManager.class, INFO_SERVICE); 101 mapping.put(CarAppFocusManager.class, APP_FOCUS_SERVICE); 102 mapping.put(CarPackageManager.class, PACKAGE_SERVICE); 103 mapping.put(CarAudioManager.class, AUDIO_SERVICE); 104 mapping.put(CarNavigationStatusManager.class, NAVIGATION_STATUS_SERVICE); 105 106 CLASS_TO_SERVICE_NAME = Collections.unmodifiableMap(mapping); 107 } 108 109 110 /** 111 * Type of car connection: car emulator, no physical connection. 112 * @hide 113 */ 114 public static final int CONNECTION_TYPE_EMULATOR = 0; 115 /** 116 * Type of car connection: connected to a car via USB. 117 * @hide 118 */ 119 public static final int CONNECTION_TYPE_USB = 1; 120 /** 121 * Type of car connection: connected to a car via Wi-Fi. 122 * @hide 123 */ 124 public static final int CONNECTION_TYPE_WIFI = 2; 125 /** 126 * Type of car connection: on-device car emulator, for development (such as Local Head Unit). 127 * @hide 128 */ 129 public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3; 130 /** 131 * Type of car connection: car emulator, connected over ADB (such as Desktop Head Unit). 132 * @hide 133 */ 134 public static final int CONNECTION_TYPE_ADB_EMULATOR = 4; 135 /** 136 * Type of car connection: platform runs directly in car. 137 * @hide 138 */ 139 public static final int CONNECTION_TYPE_EMBEDDED = 5; 140 141 /** 142 * Unknown type (the support lib is likely out-of-date). 143 * @hide 144 */ 145 public static final int CONNECTION_TYPE_UNKNOWN = -1; 146 147 private static final Set<Integer> CONNECTION_TYPES = new HashSet<>(); 148 static { 149 CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR); 150 CONNECTION_TYPES.add(CONNECTION_TYPE_USB); 151 CONNECTION_TYPES.add(CONNECTION_TYPE_WIFI); 152 CONNECTION_TYPES.add(CONNECTION_TYPE_ON_DEVICE_EMULATOR); 153 CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR); 154 CONNECTION_TYPES.add(CONNECTION_TYPE_EMBEDDED); 155 } 156 157 /** @hide */ 158 @IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI, 159 CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR, 160 CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_UNKNOWN}) 161 @Retention(RetentionPolicy.SOURCE) 162 public @interface ConnectionType { 163 } 164 165 /** 166 * Permission necessary to access car mileage information. 167 * @hide 168 */ 169 public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE"; 170 /** 171 * Permission necessary to access car fuel level. 172 * @hide 173 */ 174 public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL"; 175 /** 176 * Permission necessary to access car speed. 177 * @hide 178 */ 179 public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; 180 /** 181 * Permission necessary to access car dynamics state. 182 * @hide 183 */ 184 public static final String PERMISSION_VEHICLE_DYNAMICS_STATE = 185 "android.car.permission.VEHICLE_DYNAMICS_STATE"; 186 /** 187 * Permission necessary to access a car-specific communication channel. 188 */ 189 public static final String PERMISSION_VENDOR_EXTENSION = 190 "android.car.permission.CAR_VENDOR_EXTENSION"; 191 /** 192 * Permission necessary to use {@link android.car.navigation.CarNavigationStatusManager}. 193 */ 194 public static final String PERMISSION_CAR_NAVIGATION_MANAGER = 195 "android.car.permission.PERMISSION_CAR_NAVIGATION_MANAGER"; 196 197 198 /** 199 * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L. 200 */ 201 private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 202 203 /** 204 * {@link CarServiceLoader} implementation for projected mode. Available only when the 205 * projected client library is linked. 206 */ 207 private static final String PROJECTED_CAR_SERVICE_LOADER = 208 "com.google.android.apps.auto.sdk.service.CarServiceLoaderGms"; 209 /** 210 * Permission necessary to change car audio volume through {@link CarAudioManager}. 211 * @hide 212 */ 213 public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = 214 "android.car.permission.CAR_CONTROL_AUDIO_VOLUME"; 215 216 private final Context mContext; 217 private final Handler mEventHandler; 218 private static final int STATE_DISCONNECTED = 0; 219 private static final int STATE_CONNECTING = 1; 220 private static final int STATE_CONNECTED = 2; 221 // @GuardedBy("this") 222 private int mConnectionState; 223 224 private final CarServiceLoader.CarConnectionCallbackProxy mCarConnectionCallbackProxy = 225 new CarServiceLoader.CarConnectionCallbackProxy() { 226 @Override 227 public void onConnected() { 228 synchronized (Car.this) { 229 mConnectionState = STATE_CONNECTED; 230 } 231 mCarConnectionCallback.onConnected(Car.this); 232 } 233 234 @Override 235 public void onDisconnected() { 236 synchronized (Car.this) { 237 if (mConnectionState == STATE_DISCONNECTED) { 238 return; 239 } 240 mConnectionState = STATE_DISCONNECTED; 241 } 242 mCarConnectionCallback.onDisconnected(Car.this); 243 } 244 }; 245 246 private final CarConnectionCallback mCarConnectionCallback; 247 private final Object mCarManagerLock = new Object(); 248 //@GuardedBy("mCarManagerLock") 249 private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>(); 250 private final CarServiceLoader mCarServiceLoader; 251 252 253 /** 254 * A factory method that creates a Car instance with the given {@code Looper}. 255 * 256 * @param context The current app context. 257 * @param carConnectionCallback Receives information when the Car Service is started and 258 * stopped. 259 * @param handler The handler on which the callback should execute, or null to execute on the 260 * service's main thread. Note the service connection listener is always on the main 261 * thread regardless of the handler given. 262 * @return Car instance if system is in car environment; returns {@code null} otherwise. 263 */ 264 public static Car createCar(Context context, 265 CarConnectionCallback carConnectionCallback, @Nullable Handler handler) { 266 try { 267 return new Car(context, carConnectionCallback, handler); 268 } catch (IllegalArgumentException e) { 269 // Expected when Car Service loader is not available. 270 Log.w(TAG, "Car failed to be created", e); 271 } 272 return null; 273 } 274 275 /** 276 * A factory method that creates Car instance using the main thread {@link Handler}. 277 * 278 * @see #createCar(Context, CarConnectionCallback, Handler) 279 */ 280 public static Car createCar(Context context, 281 CarConnectionCallback carConnectionCallback) { 282 return createCar(context, carConnectionCallback, null); 283 } 284 285 private Car(Context context, CarConnectionCallback carConnectionCallback, 286 @Nullable Handler handler) { 287 mContext = context; 288 mCarConnectionCallback = carConnectionCallback; 289 if (handler == null) { 290 Looper looper = Looper.getMainLooper(); 291 handler = new Handler(looper); 292 } 293 mEventHandler = handler; 294 295 if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) { 296 mCarServiceLoader = 297 new CarServiceLoaderEmbedded(context, mCarConnectionCallbackProxy, 298 mEventHandler); 299 } else { 300 mCarServiceLoader = loadCarServiceLoader(PROJECTED_CAR_SERVICE_LOADER, context, 301 mCarConnectionCallbackProxy, mEventHandler); 302 } 303 } 304 305 private CarServiceLoader loadCarServiceLoader(String carServiceLoaderClassName, Context context, 306 CarServiceLoader.CarConnectionCallbackProxy carConnectionCallbackProxy, 307 Handler eventHandler) throws IllegalArgumentException { 308 Class<? extends CarServiceLoader> carServiceLoaderClass = null; 309 try { 310 carServiceLoaderClass = 311 Class.forName(carServiceLoaderClassName).asSubclass(CarServiceLoader.class); 312 } catch (ClassNotFoundException e) { 313 throw new IllegalArgumentException( 314 "Cannot find CarServiceLoader implementation:" + carServiceLoaderClassName, e); 315 } 316 Constructor<? extends CarServiceLoader> ctor; 317 try { 318 ctor = carServiceLoaderClass.getDeclaredConstructor(Context.class, 319 CarServiceLoader.CarConnectionCallbackProxy.class, Handler.class); 320 } catch (NoSuchMethodException e) { 321 throw new IllegalArgumentException("Cannot construct CarServiceLoader, no constructor: " 322 + carServiceLoaderClassName, e); 323 } 324 try { 325 return ctor.newInstance(context, carConnectionCallbackProxy, eventHandler); 326 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 327 | InvocationTargetException e) { 328 throw new IllegalArgumentException( 329 "Cannot construct CarServiceLoader, constructor failed for " 330 + carServiceLoaderClass.getName(), e); 331 } 332 } 333 334 /** 335 * Car constructor when CarServiceLoader is already available. 336 * 337 * @param serviceLoader must be non-null and connected or {@link CarNotConnectedException} will 338 * be thrown. 339 * @hide 340 */ 341 public Car(@NonNull CarServiceLoader serviceLoader) throws CarNotConnectedException { 342 if (!serviceLoader.isConnected()) { 343 throw new CarNotConnectedException(); 344 } 345 mCarServiceLoader = serviceLoader; 346 mEventHandler = serviceLoader.getEventHandler(); 347 mContext = serviceLoader.getContext(); 348 349 mConnectionState = STATE_CONNECTED; 350 mCarConnectionCallback = null; 351 } 352 353 /** 354 * Connect to Car Service. Can be called while disconnected. 355 * 356 * @throws IllegalStateException if the car is connected or still trying to connect 357 * from previous calls. 358 */ 359 public void connect() throws IllegalStateException { 360 synchronized (this) { 361 if (mConnectionState != STATE_DISCONNECTED) { 362 throw new IllegalStateException("already connected or connecting"); 363 } 364 mConnectionState = STATE_CONNECTING; 365 mCarServiceLoader.connect(); 366 } 367 } 368 369 /** 370 * Disconnect from Car Service. Can be called while disconnected. After disconnect is 371 * called, all Car*Managers from this instance become invalid, and {@link 372 * Car#getCarManager(String)} returns a different instance if connected again. 373 */ 374 public void disconnect() { 375 synchronized (this) { 376 tearDownCarManagers(); 377 mConnectionState = STATE_DISCONNECTED; 378 mCarServiceLoader.disconnect(); 379 } 380 } 381 382 /** 383 * @return Returns {@code true} if this object is connected to the service; {@code false} 384 * otherwise. 385 */ 386 public boolean isConnected() { 387 synchronized (this) { 388 return mConnectionState == STATE_CONNECTED; 389 } 390 } 391 392 /** 393 * @return Returns {@code true} if this object is still connecting to the service. 394 */ 395 public boolean isConnecting() { 396 synchronized (this) { 397 return mConnectionState == STATE_CONNECTING; 398 } 399 } 400 401 /** 402 * Get a car-specific manager. This is modeled after {@link Context#getSystemService(String)}. 403 * The returned {@link Object} should be type cast to the desired manager. For example, 404 * to get the sensor service, use the following: 405 * <pre>{@code CarSensorManager sensorManager = 406 * (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);}</pre> 407 * 408 * @param serviceName Name of service to create, for example {@link #SENSOR_SERVICE}. 409 * @return The requested service manager or null if the service is not available. 410 */ 411 public Object getCarManager(String serviceName) 412 throws CarNotConnectedException { 413 Object manager = null; 414 synchronized (mCarManagerLock) { 415 manager = mServiceMap.get(serviceName); 416 if (manager == null) { 417 manager = mCarServiceLoader.getCarManager(serviceName); 418 } 419 // do not store if it is not CarManagerBase. This can happen when system version 420 // is retrieved from this call. 421 if (manager != null && manager instanceof CarManagerBase) { 422 mServiceMap.put(serviceName, (CarManagerBase) manager); 423 } 424 } 425 return manager; 426 } 427 428 /** 429 * Get a car-specific manager. This is modeled after {@link Context#getSystemService(Class)}. 430 * The returned service will be type cast to the desired manager. For example, 431 * to get the sensor service, use the following: 432 * <pre>{@code CarSensorManager sensorManager = car.getCarManager(CarSensorManager.class); 433 * }</pre> 434 * 435 * @param serviceClass Class: The class of the desired service. For 436 * example {@link CarSensorManager}. 437 * @return The service or null if the class is not a supported car service. 438 */ 439 public <T> T getCarManager(Class<T> serviceClass) throws CarNotConnectedException { 440 // TODO(jthol) port to a more robust registry implementation 441 String serviceName = CLASS_TO_SERVICE_NAME.get(serviceClass); 442 return (serviceName == null) ? null : (T) getCarManager(serviceName); 443 } 444 445 /** 446 * Return the type of currently connected car. This should only be used for testing scenarios 447 * 448 * @return One of {@link #CONNECTION_TYPE_USB}, {@link #CONNECTION_TYPE_WIFI}, 449 * {@link #CONNECTION_TYPE_EMBEDDED}, {@link #CONNECTION_TYPE_ON_DEVICE_EMULATOR}, 450 * {@link #CONNECTION_TYPE_ADB_EMULATOR}, 451 * {@link #CONNECTION_TYPE_UNKNOWN}. 452 * @throws CarNotConnectedException if the connection to the car service has been lost. 453 * @hide 454 */ 455 @ConnectionType 456 public int getCarConnectionType() throws CarNotConnectedException { 457 int carConnectionType = mCarServiceLoader.getCarConnectionType(); 458 if (!CONNECTION_TYPES.contains(carConnectionType)){ 459 return CONNECTION_TYPE_UNKNOWN; 460 } 461 return carConnectionType; 462 } 463 464 private void tearDownCarManagers() { 465 synchronized (mCarManagerLock) { 466 for (CarManagerBase manager : mServiceMap.values()) { 467 manager.onCarDisconnected(); 468 } 469 mServiceMap.clear(); 470 } 471 } 472 } 473