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