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