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