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.car;
     18 
     19 import android.annotation.IntDef;
     20 import android.annotation.Nullable;
     21 import android.annotation.SystemApi;
     22 import android.car.content.pm.CarPackageManager;
     23 import android.car.hardware.CarSensorManager;
     24 import android.car.hardware.camera.CarCameraManager;
     25 import android.car.hardware.hvac.CarHvacManager;
     26 import android.car.hardware.radio.CarRadioManager;
     27 import android.car.media.CarAudioManager;
     28 import android.car.navigation.CarNavigationManager;
     29 import android.car.test.CarTestManagerBinderWrapper;
     30 import android.content.ComponentName;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.ServiceConnection;
     34 import android.content.pm.PackageManager;
     35 import android.os.Handler;
     36 import android.os.IBinder;
     37 import android.os.Looper;
     38 import android.os.RemoteException;
     39 import android.os.UserHandle;
     40 import android.util.Log;
     41 
     42 import com.android.internal.annotations.GuardedBy;
     43 
     44 import java.lang.annotation.Retention;
     45 import java.lang.annotation.RetentionPolicy;
     46 import java.util.HashMap;
     47 
     48 /**
     49  *   Top level car API for embedded Android Auto deployments.
     50  *   This API works only for devices with {@link PackageManager#FEATURE_AUTOMOTIVE}
     51  *   Calling this API on a device with no such feature will lead to an exception.
     52  */
     53 public class Car {
     54 
     55     /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */
     56     public static final String SENSOR_SERVICE = "sensor";
     57 
     58     /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */
     59     public static final String INFO_SERVICE = "info";
     60 
     61     /** Service name for {@link CarAppContextManager}. */
     62     public static final String APP_CONTEXT_SERVICE = "app_context";
     63 
     64     /** Service name for {@link CarPackageManager} */
     65     public static final String PACKAGE_SERVICE = "package";
     66 
     67     /** Service name for {@link CarAudioManager} */
     68     public static final String AUDIO_SERVICE = "audio";
     69     /**
     70      * Service name for {@link CarNavigationManager}
     71      * @hide
     72      */
     73     public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
     74 
     75     /**
     76      * @hide
     77      */
     78     @SystemApi
     79     public static final String CAMERA_SERVICE = "camera";
     80 
     81     /**
     82      * @hide
     83      */
     84     @SystemApi
     85     public static final String RADIO_SERVICE = "radio";
     86 
     87     /**
     88      * @hide
     89      */
     90     @SystemApi
     91     public static final String HVAC_SERVICE = "hvac";
     92 
     93     /**
     94      * @hide
     95      */
     96     @SystemApi
     97     public static final String PROJECTION_SERVICE = "projection";
     98 
     99     /**
    100      * Service for testing. This is system app only feature.
    101      * Service name for {@link CarTestManager}, to be used in {@link #getCarManager(String)}.
    102      * @hide
    103      */
    104     @SystemApi
    105     public static final String TEST_SERVICE = "car-service-test";
    106 
    107     /** Permission necessary to access car's mileage information. */
    108     public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
    109 
    110     /** Permission necessary to access car's fuel level. */
    111     public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
    112 
    113     /** Permission necessary to access car's speed. */
    114     public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
    115 
    116     /**
    117      * Permission necessary to use {@link CarNavigationManager}.
    118      * @hide
    119      */
    120     public static final String PERMISSION_CAR_NAVIGATION_MANAGER =
    121             "android.car.permission.CAR_NAVIGATION_MANAGER";
    122 
    123     /**
    124      * Permission necessary to access car specific communication channel.
    125      * @hide
    126      */
    127     @SystemApi
    128     public static final String PERMISSION_VENDOR_EXTENSION =
    129             "android.car.permission.CAR_VENDOR_EXTENSION";
    130 
    131     /**
    132      * @hide
    133      */
    134     @SystemApi
    135     public static final String PERMISSION_CONTROL_APP_BLOCKING =
    136             "android.car.permission.CONTROL_APP_BLOCKING";
    137 
    138     /**
    139      * Permission necessary to access Car Camera APIs.
    140      * @hide
    141      */
    142     @SystemApi
    143     public static final String PERMISSION_CAR_CAMERA = "android.car.permission.CAR_CAMERA";
    144 
    145     /**
    146      * Permission necessary to access Car HVAC APIs.
    147      * @hide
    148      */
    149     @SystemApi
    150     public static final String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC";
    151 
    152     /**
    153      * Permission necessary to access Car RADIO system APIs.
    154      * @hide
    155      */
    156     @SystemApi
    157     public static final String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
    158 
    159     /**
    160      * Permission necesary to access Car PRJECTION system APIs.
    161      * @hide
    162      */
    163     @SystemApi
    164     public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
    165 
    166     /**
    167      * Permission necessary to mock vehicle hal for testing.
    168      * @hide
    169      */
    170     @SystemApi
    171     public static final String PERMISSION_MOCK_VEHICLE_HAL =
    172             "android.car.permission.CAR_MOCK_VEHICLE_HAL";
    173 
    174     /** Type of car connection: platform runs directly in car. */
    175     public static final int CONNECTION_TYPE_EMBEDDED = 5;
    176     /**
    177      * Type of car connection: platform runs directly in car but with mocked vehicle hal.
    178      * This will only happen in testing environment.
    179      * @hide
    180      */
    181     public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6;
    182 
    183 
    184     /** @hide */
    185     @IntDef({CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_EMBEDDED_MOCKING})
    186     @Retention(RetentionPolicy.SOURCE)
    187     public @interface ConnectionType {}
    188 
    189     /**
    190      * CarXyzService throws IllegalStateException with this message is re-thrown as
    191      * {@link CarNotConnectedException}.
    192      *
    193      * @hide
    194      */
    195     public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected";
    196 
    197     /** @hide */
    198     public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar";
    199 
    200     private static final String CAR_SERVICE_PACKAGE = "com.android.car";
    201 
    202     private static final String CAR_SERVICE_CLASS = "com.android.car.CarService";
    203 
    204     private static final String CAR_TEST_MANAGER_CLASS = "android.car.CarTestManager";
    205 
    206     private static final long CAR_SERVICE_BIND_RETRY_INTERVAL_MS = 500;
    207     private static final long CAR_SERVICE_BIND_MAX_RETRY = 20;
    208 
    209     private final Context mContext;
    210     private final Looper mLooper;
    211     @GuardedBy("this")
    212     private ICar mService;
    213     private static final int STATE_DISCONNECTED = 0;
    214     private static final int STATE_CONNECTING = 1;
    215     private static final int STATE_CONNECTED = 2;
    216     @GuardedBy("this")
    217     private int mConnectionState;
    218     @GuardedBy("this")
    219     private int mConnectionRetryCount;
    220 
    221     private final Runnable mConnectionRetryRunnable = new Runnable() {
    222         @Override
    223         public void run() {
    224             startCarService();
    225         }
    226     };
    227 
    228     private final Runnable mConnectionRetryFailedRunnable = new Runnable() {
    229         @Override
    230         public void run() {
    231             mServiceConnectionListener.onServiceDisconnected(new ComponentName(CAR_SERVICE_PACKAGE,
    232                     CAR_SERVICE_CLASS));
    233         }
    234     };
    235 
    236     private final ServiceConnection mServiceConnectionListener =
    237             new ServiceConnection () {
    238         public void onServiceConnected(ComponentName name, IBinder service) {
    239             synchronized (Car.this) {
    240                 mService = ICar.Stub.asInterface(service);
    241                 mConnectionState = STATE_CONNECTED;
    242             }
    243             mServiceConnectionListenerClient.onServiceConnected(name, service);
    244         }
    245 
    246         public void onServiceDisconnected(ComponentName name) {
    247             synchronized (Car.this) {
    248                 mService = null;
    249                 if (mConnectionState  == STATE_DISCONNECTED) {
    250                     return;
    251                 }
    252                 mConnectionState = STATE_DISCONNECTED;
    253             }
    254             // unbind explicitly here.
    255             disconnect();
    256             mServiceConnectionListenerClient.onServiceDisconnected(name);
    257         }
    258     };
    259 
    260     private final ServiceConnection mServiceConnectionListenerClient;
    261     private final Object mCarManagerLock = new Object();
    262     @GuardedBy("mCarManagerLock")
    263     private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
    264 
    265     /** Handler for generic event dispatching. */
    266     private final Handler mEventHandler;
    267 
    268     private final Handler mMainThreadEvetHandler;
    269 
    270     /**
    271      * A factory method that creates Car instance for all Car API access.
    272      * @param context
    273      * @param serviceConnectionListener listener for monitoring service connection.
    274      * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that
    275      *        service connection listener will be always in main thread regardless of this Looper.
    276      * @return Car instance if system is in car environment and returns {@code null} otherwise.
    277      */
    278     public static Car createCar(Context context, ServiceConnection serviceConnectionListener,
    279             @Nullable Looper looper) {
    280         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
    281             Log.e(CarLibLog.TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used");
    282             return null;
    283         }
    284         try {
    285           return new Car(context, serviceConnectionListener, looper);
    286         } catch (IllegalArgumentException e) {
    287           // Expected when car service loader is not available.
    288         }
    289         return null;
    290     }
    291 
    292     /**
    293      * A factory method that creates Car instance for all Car API access using main thread {@code
    294      * Looper}.
    295      *
    296      * @see #createCar(Context, ServiceConnection, Looper)
    297      */
    298     public static Car createCar(Context context, ServiceConnection serviceConnectionListener) {
    299       return createCar(context, serviceConnectionListener, null);
    300     }
    301 
    302     private Car(Context context, ServiceConnection serviceConnectionListener,
    303             @Nullable Looper looper) {
    304         mContext = context;
    305         mServiceConnectionListenerClient = serviceConnectionListener;
    306         if (looper == null) {
    307             mLooper = Looper.getMainLooper();
    308         } else {
    309             mLooper = looper;
    310         }
    311         mEventHandler = new Handler(mLooper);
    312         if (mLooper == Looper.getMainLooper()) {
    313             mMainThreadEvetHandler = mEventHandler;
    314         } else {
    315             mMainThreadEvetHandler = new Handler(Looper.getMainLooper());
    316         }
    317     }
    318 
    319     /**
    320      * Car constructor when ICar binder is already available.
    321      * @param context
    322      * @param service
    323      * @param looper
    324      *
    325      * @hide
    326      */
    327     public Car(Context context, ICar service, @Nullable Looper looper) {
    328         mContext = context;
    329         if (looper == null) {
    330             mLooper = Looper.getMainLooper();
    331         } else {
    332             mLooper = looper;
    333         }
    334         mEventHandler = new Handler(mLooper);
    335         if (mLooper == Looper.getMainLooper()) {
    336             mMainThreadEvetHandler = mEventHandler;
    337         } else {
    338             mMainThreadEvetHandler = new Handler(Looper.getMainLooper());
    339         }
    340         mService = service;
    341         mConnectionState = STATE_CONNECTED;
    342         mServiceConnectionListenerClient = null;
    343     }
    344 
    345     /**
    346      * Connect to car service. This can be called while it is disconnected.
    347      * @throws IllegalStateException If connection is still on-going from previous
    348      *         connect call or it is already connected
    349      */
    350     public void connect() throws IllegalStateException {
    351         synchronized (this) {
    352             if (mConnectionState != STATE_DISCONNECTED) {
    353                 throw new IllegalStateException("already connected or connecting");
    354             }
    355             mConnectionState = STATE_CONNECTING;
    356             startCarService();
    357         }
    358     }
    359 
    360     /**
    361      * Disconnect from car service. This can be called while disconnected. Once disconnect is
    362      * called, all Car*Managers from this instance becomes invalid, and
    363      * {@link Car#getCarManager(String)} will return different instance if it is connected again.
    364      */
    365     public void disconnect() {
    366         synchronized (this) {
    367             if (mConnectionState == STATE_DISCONNECTED) {
    368                 return;
    369             }
    370             mEventHandler.removeCallbacks(mConnectionRetryRunnable);
    371             mMainThreadEvetHandler.removeCallbacks(mConnectionRetryFailedRunnable);
    372             mConnectionRetryCount = 0;
    373             tearDownCarManagers();
    374             mService = null;
    375             mConnectionState = STATE_DISCONNECTED;
    376             mContext.unbindService(mServiceConnectionListener);
    377         }
    378     }
    379 
    380     /**
    381      * Tells if it is connected to the service or not. This will return false if it is still
    382      * connecting.
    383      * @return
    384      */
    385     public boolean isConnected() {
    386         synchronized (this) {
    387             return mService != null;
    388         }
    389     }
    390 
    391     /**
    392      * Tells if this instance is already connecting to car service or not.
    393      * @return
    394      */
    395     public boolean isConnecting() {
    396         synchronized (this) {
    397             return mConnectionState == STATE_CONNECTING;
    398         }
    399     }
    400 
    401     /**
    402      * Get car specific service as in {@link Context#getSystemService(String)}. Returned
    403      * {@link Object} should be type-casted to the desired service.
    404      * For example, to get sensor service,
    405      * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);
    406      * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}.
    407      * @return Matching service manager or null if there is no such service.
    408      * @throws CarNotConnectedException
    409      */
    410     public Object getCarManager(String serviceName) throws CarNotConnectedException {
    411         CarManagerBase manager = null;
    412         ICar service = getICarOrThrow();
    413         synchronized (mCarManagerLock) {
    414             manager = mServiceMap.get(serviceName);
    415             if (manager == null) {
    416                 try {
    417                     IBinder binder = service.getCarService(serviceName);
    418                     if (binder == null) {
    419                         Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" +
    420                                 serviceName);
    421                         return null;
    422                     }
    423                     manager = createCarManager(serviceName, binder);
    424                     if (manager == null) {
    425                         Log.w(CarLibLog.TAG_CAR,
    426                                 "getCarManager could not create manager for service:" +
    427                                 serviceName);
    428                         return null;
    429                     }
    430                     mServiceMap.put(serviceName, manager);
    431                 } catch (RemoteException e) {
    432                     handleRemoteException(e);
    433                 }
    434             }
    435         }
    436         return manager;
    437     }
    438 
    439     /**
    440      * Return the type of currently connected car.
    441      * @return
    442      */
    443     @ConnectionType
    444     public int getCarConnectionType() {
    445         return CONNECTION_TYPE_EMBEDDED;
    446     }
    447 
    448     /**
    449      * IllegalStateException from XyzCarService with special message is re-thrown as a different
    450      * exception. If the IllegalStateException is not understood then this message will throw the
    451      * original exception.
    452      *
    453      * @param e exception from XyzCarService.
    454      * @throws CarNotConnectedException
    455      * @hide
    456      */
    457     public static void checkCarNotConnectedExceptionFromCarService(
    458             IllegalStateException e) throws CarNotConnectedException, IllegalStateException {
    459         String message = e.getMessage();
    460         if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) {
    461             throw new CarNotConnectedException();
    462         } else {
    463             throw e;
    464         }
    465     }
    466 
    467     private CarManagerBase createCarManager(String serviceName, IBinder binder)
    468             throws CarNotConnectedException {
    469         CarManagerBase manager = null;
    470         switch (serviceName) {
    471             case AUDIO_SERVICE:
    472                 manager = new CarAudioManager(binder, mContext);
    473                 break;
    474             case SENSOR_SERVICE:
    475                 manager = new CarSensorManager(binder, mContext, mLooper);
    476                 break;
    477             case INFO_SERVICE:
    478                 manager = new CarInfoManager(binder);
    479                 break;
    480             case APP_CONTEXT_SERVICE:
    481                 manager = new CarAppContextManager(binder, mLooper);
    482                 break;
    483             case PACKAGE_SERVICE:
    484                 manager = new CarPackageManager(binder, mContext);
    485                 break;
    486             case CAR_NAVIGATION_SERVICE:
    487                 manager = new CarNavigationManager(binder, mLooper);
    488                 break;
    489             case CAMERA_SERVICE:
    490                 manager = new CarCameraManager(binder, mContext);
    491                 break;
    492             case HVAC_SERVICE:
    493                 manager = new CarHvacManager(binder, mContext, mLooper);
    494                 break;
    495             case PROJECTION_SERVICE:
    496                 manager = new CarProjectionManager(binder, mLooper);
    497                 break;
    498             case RADIO_SERVICE:
    499                 manager = new CarRadioManager(binder, mLooper);
    500                 break;
    501             case TEST_SERVICE:
    502                 /* CarTestManager exist in static library. So instead of constructing it here,
    503                  * only pass binder wrapper so that CarTestManager can be constructed outside. */
    504                 manager = new CarTestManagerBinderWrapper(binder);
    505                 break;
    506         }
    507         return manager;
    508     }
    509 
    510     private void startCarService() {
    511         Intent intent = new Intent();
    512         intent.setPackage(CAR_SERVICE_PACKAGE);
    513         intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
    514         boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener,
    515                 Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF);
    516         if (!bound) {
    517             mConnectionRetryCount++;
    518             if (mConnectionRetryCount > CAR_SERVICE_BIND_MAX_RETRY) {
    519                 Log.w(CarLibLog.TAG_CAR, "cannot bind to car service after max retry");
    520                 mMainThreadEvetHandler.post(mConnectionRetryFailedRunnable);
    521             } else {
    522                 mEventHandler.postDelayed(mConnectionRetryRunnable,
    523                         CAR_SERVICE_BIND_RETRY_INTERVAL_MS);
    524             }
    525         } else {
    526             mConnectionRetryCount = 0;
    527         }
    528     }
    529 
    530     private synchronized ICar getICarOrThrow() throws IllegalStateException {
    531         if (mService == null) {
    532             throw new IllegalStateException("not connected");
    533         }
    534         return mService;
    535     }
    536 
    537     private void handleRemoteException(RemoteException e) {
    538         Log.w(CarLibLog.TAG_CAR, "RemoteException", e);
    539         disconnect();
    540     }
    541 
    542     private void tearDownCarManagers() {
    543         synchronized (mCarManagerLock) {
    544             for (CarManagerBase manager: mServiceMap.values()) {
    545                 manager.onCarDisconnected();
    546             }
    547             mServiceMap.clear();
    548         }
    549     }
    550 }
    551