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.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