Home | History | Annotate | Download | only in car
      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