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.support.annotation.IntDef; 20 import android.support.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.pm.PackageManager; 24 import android.os.Handler; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.os.RemoteException; 28 import android.support.car.content.pm.CarPackageManager; 29 import android.support.car.hardware.CarSensorManager; 30 import android.support.car.navigation.CarNavigationManager; 31 import android.util.Log; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.lang.ref.WeakReference; 36 import java.lang.reflect.Constructor; 37 import java.lang.reflect.InvocationTargetException; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.LinkedList; 41 42 /** 43 * Top level car API. 44 * This API works only for device with {@link PackageManager#FEATURE_AUTOMOTIVE} feature 45 * supported or device with Google play service. 46 * Calling this API with device with no such feature will lead into an exception. 47 * 48 */ 49 public class Car { 50 51 /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */ 52 public static final String SENSOR_SERVICE = "sensor"; 53 54 /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */ 55 public static final String INFO_SERVICE = "info"; 56 57 /** Service name for {@link CarAppContextManager}. */ 58 public static final String APP_CONTEXT_SERVICE = "app_context"; 59 60 /** Service name for {@link CarPackageManager} */ 61 public static final String PACKAGE_SERVICE = "package"; 62 63 /** Service name for {@link CarAudioManager} */ 64 public static final String AUDIO_SERVICE = "audio"; 65 /** 66 * Service name for {@link CarNavigationManager} 67 * @hide 68 */ 69 public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service"; 70 71 /** Type of car connection: car emulator, not physical connection. */ 72 public static final int CONNECTION_TYPE_EMULATOR = 0; 73 /** Type of car connection: connected to a car via USB. */ 74 public static final int CONNECTION_TYPE_USB = 1; 75 /** Type of car connection: connected to a car via WIFI. */ 76 public static final int CONNECTION_TYPE_WIFI = 2; 77 /** Type of car connection: on-device car emulator, for development (e.g. Local Head Unit). */ 78 public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3; 79 /** Type of car connection: car emulator, connected over ADB (e.g. Desktop Head Unit). */ 80 public static final int CONNECTION_TYPE_ADB_EMULATOR = 4; 81 /** Type of car connection: platform runs directly in car. */ 82 public static final int CONNECTION_TYPE_EMBEDDED = 5; 83 /** 84 * Type of car connection: platform runs directly in car but with mocked vehicle hal. 85 * This will only happen in testing environment. 86 * @hide 87 */ 88 public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6; 89 90 /** @hide */ 91 @IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI, 92 CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR, CONNECTION_TYPE_EMBEDDED}) 93 @Retention(RetentionPolicy.SOURCE) 94 public @interface ConnectionType {} 95 96 /** Permission necessary to access car's mileage information. */ 97 public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE"; 98 /** Permission necessary to access car's fuel level. */ 99 public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL"; 100 /** Permission necessary to access car's speed. */ 101 public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; 102 /** Permission necessary to access car specific communication channel. */ 103 public static final String PERMISSION_VENDOR_EXTENSION = 104 "android.car.permission.CAR_VENDOR_EXTENSION"; 105 /** 106 * Permission necessary to use {@link android.car.navigation.CarNavigationManager}. 107 * @hide 108 */ 109 public static final String PERMISSION_CAR_NAVIGATION_MANAGER = 110 "android.car.permission.PERMISSION_CAR_NAVIGATION_MANAGER"; 111 112 113 /** 114 * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L. 115 * @hide 116 */ 117 private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; 118 119 /** 120 * {@link CarServiceLoader} implementation for projected mode. Only available when projected 121 * client library is linked. 122 * @hide 123 */ 124 private static final String PROJECTED_CAR_SERVICE_LOADER = 125 "com.google.android.gms.car.CarServiceLoaderGms"; 126 127 /** 128 * CarXyzService throws IllegalStateException with this message is re-thrown as 129 * {@link CarNotConnectedException}. 130 * 131 * @hide 132 */ 133 public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected"; 134 135 /** @hide */ 136 public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar"; 137 138 private final Context mContext; 139 private final Looper mLooper; 140 private static final int STATE_DISCONNECTED = 0; 141 private static final int STATE_CONNECTING = 1; 142 private static final int STATE_CONNECTED = 2; 143 // @GuardedBy("this") 144 private int mConnectionState; 145 146 private final ServiceConnectionListener mServiceConnectionListener = 147 new ServiceConnectionListener () { 148 public void onServiceConnected(ComponentName name) { 149 synchronized (Car.this) { 150 mConnectionState = STATE_CONNECTED; 151 } 152 mServiceConnectionListenerClient.onServiceConnected(name); 153 } 154 155 public void onServiceDisconnected(ComponentName name) { 156 synchronized (Car.this) { 157 if (mConnectionState == STATE_DISCONNECTED) { 158 return; 159 } 160 mConnectionState = STATE_DISCONNECTED; 161 } 162 mServiceConnectionListenerClient.onServiceDisconnected(name); 163 connect(); 164 } 165 166 public void onServiceSuspended(int cause) { 167 mServiceConnectionListenerClient.onServiceSuspended(cause); 168 } 169 170 public void onServiceConnectionFailed(int cause) { 171 mServiceConnectionListenerClient.onServiceConnectionFailed(cause); 172 } 173 }; 174 175 private final ServiceConnectionListener mServiceConnectionListenerClient; 176 private final Object mCarManagerLock = new Object(); 177 //@GuardedBy("mCarManagerLock") 178 private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>(); 179 private final CarServiceLoader mCarServiceLoader; 180 181 /** Handler for generic event dispatching. */ 182 private final Handler mEventHandler; 183 184 /** 185 * A factory method that creates Car instance for all Car API access. 186 * @param context 187 * @param serviceConnectionListener listener for monitoring service connection. 188 * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that 189 * service connection listener will be always in main thread regardless of this Looper. 190 * @return Car instance if system is in car environment and returns {@code null} otherwise. 191 */ createCar(Context context, ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper)192 public static Car createCar(Context context, 193 ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper) { 194 try { 195 return new Car(context, serviceConnectionListener, looper); 196 } catch (IllegalArgumentException e) { 197 // Expected when car service loader is not available. 198 } 199 return null; 200 } 201 202 /** 203 * A factory method that creates Car instance for all Car API access using main thread {@code 204 * Looper}. 205 * 206 * @see #createCar(Context, ServiceConnectionListener, Looper) 207 */ createCar(Context context, ServiceConnectionListener serviceConnectionListener)208 public static Car createCar(Context context, 209 ServiceConnectionListener serviceConnectionListener) { 210 return createCar(context, serviceConnectionListener, null); 211 } 212 Car(Context context, ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper)213 private Car(Context context, ServiceConnectionListener serviceConnectionListener, 214 @Nullable Looper looper) { 215 mContext = context; 216 mServiceConnectionListenerClient = serviceConnectionListener; 217 if (looper == null) { 218 mLooper = Looper.getMainLooper(); 219 } else { 220 mLooper = looper; 221 } 222 mEventHandler = new Handler(mLooper); 223 if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) { 224 mCarServiceLoader = new CarServiceLoaderEmbedded(context, mServiceConnectionListener, 225 mLooper); 226 } else { 227 mCarServiceLoader = loadCarServiceLoader(PROJECTED_CAR_SERVICE_LOADER, context, 228 mServiceConnectionListener, mLooper); 229 } 230 } 231 loadCarServiceLoader(String carServiceLoaderClassName, Context context, ServiceConnectionListener serviceConnectionListener, Looper looper)232 private CarServiceLoader loadCarServiceLoader(String carServiceLoaderClassName, 233 Context context, ServiceConnectionListener serviceConnectionListener, Looper looper) 234 throws IllegalArgumentException { 235 Class carServiceLoaderClass = null; 236 try { 237 carServiceLoaderClass = Class.forName(carServiceLoaderClassName); 238 } catch (ClassNotFoundException e) { 239 throw new IllegalArgumentException("Cannot find CarServiceLoader implementation:" + 240 carServiceLoaderClassName, e); 241 } 242 Constructor<?> ctor; 243 try { 244 ctor = carServiceLoaderClass.getDeclaredConstructor(Context.class, 245 ServiceConnectionListener.class, Looper.class); 246 } catch (NoSuchMethodException e) { 247 throw new IllegalArgumentException("Cannot construct CarServiceLoader, no constructor: " 248 + carServiceLoaderClassName, e); 249 } 250 try { 251 return (CarServiceLoader) ctor.newInstance(context, 252 serviceConnectionListener, looper); 253 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 254 | InvocationTargetException e) { 255 throw new IllegalArgumentException( 256 "Cannot construct CarServiceLoader, constructor failed for " 257 + carServiceLoaderClass.getName(), e); 258 } 259 } 260 261 /** 262 * Car constructor when CarServiceLoader is already available. 263 * @param context 264 * @param serviceLoader 265 * @param looper 266 * 267 * @hide 268 */ Car(Context context, CarServiceLoader serviceLoader, @Nullable Looper looper)269 public Car(Context context, CarServiceLoader serviceLoader, @Nullable Looper looper) { 270 mContext = context; 271 if (looper == null) { 272 mLooper = Looper.getMainLooper(); 273 } else { 274 mLooper = looper; 275 } 276 mEventHandler = new Handler(mLooper); 277 mConnectionState = STATE_CONNECTED; 278 mCarServiceLoader = serviceLoader; 279 mServiceConnectionListenerClient = null; 280 } 281 282 /** 283 * Connect to car service. This can be called while it is disconnected. 284 * @throws IllegalStateException If connection is still on-going from previous 285 * connect call or it is already connected 286 */ connect()287 public void connect() throws IllegalStateException { 288 synchronized (this) { 289 if (mConnectionState != STATE_DISCONNECTED) { 290 throw new IllegalStateException("already connected or connecting"); 291 } 292 mConnectionState = STATE_CONNECTING; 293 mCarServiceLoader.connect(); 294 } 295 } 296 297 /** 298 * Disconnect from car service. This can be called while disconnected. Once disconnect is 299 * called, all Car*Managers from this instance becomes invalid, and 300 * {@link Car#getCarManager(String)} will return different instance if it is connected again. 301 */ disconnect()302 public void disconnect() { 303 synchronized (this) { 304 if (mConnectionState == STATE_DISCONNECTED) { 305 return; 306 } 307 tearDownCarManagers(); 308 mConnectionState = STATE_DISCONNECTED; 309 mCarServiceLoader.disconnect(); 310 } 311 } 312 313 /** 314 * Tells if it is connected to the service or not. This will return false if it is still 315 * connecting. 316 * @return 317 */ isConnected()318 public boolean isConnected() { 319 synchronized (this) { 320 return mConnectionState == STATE_CONNECTED; 321 } 322 } 323 324 /** 325 * Tells if this instance is already connecting to car service or not. 326 * @return 327 */ isConnecting()328 public boolean isConnecting() { 329 synchronized (this) { 330 return mConnectionState == STATE_CONNECTING; 331 } 332 } 333 334 /** 335 * Tells if car is connected to car or not. In some car environments, being connected to service 336 * does not necessarily mean being connected to car. 337 */ isConnectedToCar()338 public boolean isConnectedToCar() { 339 return mCarServiceLoader.isConnectedToCar(); 340 } 341 342 /** 343 * Get car specific service as in {@link Context#getSystemService(String)}. Returned 344 * {@link Object} should be type-casted to the desired service. 345 * For example, to get sensor service, 346 * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE); 347 * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}. 348 * @return Matching service manager or null if there is no such service. 349 */ getCarManager(String serviceName)350 public Object getCarManager(String serviceName) 351 throws CarNotSupportedException, CarNotConnectedException { 352 Object manager = null; 353 synchronized (mCarManagerLock) { 354 manager = mServiceMap.get(serviceName); 355 if (manager == null) { 356 manager = mCarServiceLoader.getCarManager(serviceName); 357 } 358 // do not store if it is not CarManagerBase. This can happen when system version 359 // is retrieved from this call. 360 if (manager != null && manager instanceof CarManagerBase) { 361 mServiceMap.put(serviceName, (CarManagerBase) manager); 362 } 363 } 364 return manager; 365 } 366 367 /** 368 * Return the type of currently connected car. 369 * @return 370 * @throws CarNotConnectedException 371 */ 372 @ConnectionType getCarConnectionType()373 public int getCarConnectionType() throws CarNotConnectedException { 374 return mCarServiceLoader.getCarConnectionType(); 375 } 376 377 /** 378 * Registers a {@link CarConnectionListener}. 379 * 380 * Avoid reregistering unregistered listeners. If an unregistered listener is reregistered, 381 * it may receive duplicate calls to {@link CarConnectionListener#onConnected}. 382 * 383 * @throws IllegalStateException if service is not connected. 384 */ registerCarConnectionListener(CarConnectionListener listener)385 public void registerCarConnectionListener(CarConnectionListener listener) 386 throws IllegalStateException, CarNotConnectedException { 387 assertCarConnection(); 388 mCarServiceLoader.registerCarConnectionListener(listener); 389 } 390 391 /** 392 * Unregisters a {@link CarConnectionListener}. 393 * 394 * <b>Note:</b> If this method is called from a thread besides the client's looper thread, 395 * there is no guarantee that the unregistered listener will not receive callbacks after 396 * this method returns. 397 */ unregisterCarConnectionListener(CarConnectionListener listener)398 public void unregisterCarConnectionListener(CarConnectionListener listener) { 399 mCarServiceLoader.unregisterCarConnectionListener(listener); 400 } 401 402 /** 403 * IllegalStateException from XyzCarService with special message is re-thrown as a different 404 * exception. If the IllegalStateException is not understood then this message will throw the 405 * original exception. 406 * 407 * @param e exception from XyzCarService. 408 * @throws CarNotConnectedException 409 * @hide 410 */ checkCarNotConnectedExceptionFromCarService( IllegalStateException e)411 public static void checkCarNotConnectedExceptionFromCarService( 412 IllegalStateException e) throws CarNotConnectedException, IllegalStateException { 413 String message = e.getMessage(); 414 if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) { 415 throw new CarNotConnectedException(); 416 } else { 417 throw e; 418 } 419 } 420 assertCarConnection()421 private synchronized void assertCarConnection() throws IllegalStateException { 422 if (!mCarServiceLoader.isConnectedToCar()) { 423 throw new IllegalStateException("not connected"); 424 } 425 } 426 tearDownCarManagers()427 private void tearDownCarManagers() { 428 synchronized (mCarManagerLock) { 429 for (CarManagerBase manager: mServiceMap.values()) { 430 manager.onCarDisconnected(); 431 } 432 mServiceMap.clear(); 433 } 434 } 435 } 436