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