Car.java revision c4d442f4a0d3acf90b1c7a1dd7c222a8f32a193f
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 change car audio volume through {@link CarAudioManager}.
117     */
118    public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME =
119            "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
120
121    /**
122     * Permission necessary to use {@link CarNavigationManager}.
123     * @hide
124     */
125    public static final String PERMISSION_CAR_NAVIGATION_MANAGER =
126            "android.car.permission.CAR_NAVIGATION_MANAGER";
127
128    /**
129     * Permission necessary to access car specific communication channel.
130     * @hide
131     */
132    @SystemApi
133    public static final String PERMISSION_VENDOR_EXTENSION =
134            "android.car.permission.CAR_VENDOR_EXTENSION";
135
136    /**
137     * @hide
138     */
139    @SystemApi
140    public static final String PERMISSION_CONTROL_APP_BLOCKING =
141            "android.car.permission.CONTROL_APP_BLOCKING";
142
143    /**
144     * Permission necessary to access Car Camera APIs.
145     * @hide
146     */
147    @SystemApi
148    public static final String PERMISSION_CAR_CAMERA = "android.car.permission.CAR_CAMERA";
149
150    /**
151     * Permission necessary to access Car HVAC APIs.
152     * @hide
153     */
154    @SystemApi
155    public static final String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC";
156
157    /**
158     * Permission necessary to access Car RADIO system APIs.
159     * @hide
160     */
161    @SystemApi
162    public static final String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
163
164
165    /**
166     * Permission necessary to access Car PROJECTION system APIs.
167     * @hide
168     */
169    @SystemApi
170    public static final String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
171
172    /**
173     * Permission necessary to mock vehicle hal for testing.
174     * @hide
175     */
176    @SystemApi
177    public static final String PERMISSION_MOCK_VEHICLE_HAL =
178            "android.car.permission.CAR_MOCK_VEHICLE_HAL";
179
180    /** Type of car connection: platform runs directly in car. */
181    public static final int CONNECTION_TYPE_EMBEDDED = 5;
182    /**
183     * Type of car connection: platform runs directly in car but with mocked vehicle hal.
184     * This will only happen in testing environment.
185     * @hide
186     */
187    public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6;
188
189
190    /** @hide */
191    @IntDef({CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_EMBEDDED_MOCKING})
192    @Retention(RetentionPolicy.SOURCE)
193    public @interface ConnectionType {}
194
195    /**
196     * CarXyzService throws IllegalStateException with this message is re-thrown as
197     * {@link CarNotConnectedException}.
198     *
199     * @hide
200     */
201    public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected";
202
203    /** @hide */
204    public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar";
205
206    private static final String CAR_SERVICE_PACKAGE = "com.android.car";
207
208    private static final String CAR_TEST_MANAGER_CLASS = "android.car.CarTestManager";
209
210    private final Context mContext;
211    private final Looper mLooper;
212    @GuardedBy("this")
213    private ICar mService;
214    private static final int STATE_DISCONNECTED = 0;
215    private static final int STATE_CONNECTING = 1;
216    private static final int STATE_CONNECTED = 2;
217    @GuardedBy("this")
218    private int mConnectionState;
219
220    private final ServiceConnection mServiceConnectionListener =
221            new ServiceConnection () {
222        public void onServiceConnected(ComponentName name, IBinder service) {
223            synchronized (Car.this) {
224                mService = ICar.Stub.asInterface(service);
225                mConnectionState = STATE_CONNECTED;
226            }
227            mServiceConnectionListenerClient.onServiceConnected(name, service);
228        }
229
230        public void onServiceDisconnected(ComponentName name) {
231            synchronized (Car.this) {
232                mService = null;
233                if (mConnectionState  == STATE_DISCONNECTED) {
234                    return;
235                }
236                mConnectionState = STATE_DISCONNECTED;
237            }
238            // unbind explicitly here.
239            disconnect();
240            mServiceConnectionListenerClient.onServiceDisconnected(name);
241        }
242    };
243
244    private final ServiceConnection mServiceConnectionListenerClient;
245    private final Object mCarManagerLock = new Object();
246    @GuardedBy("mCarManagerLock")
247    private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
248
249    /** Handler for generic event dispatching. */
250    private final Handler mEventHandler;
251
252    /**
253     * A factory method that creates Car instance for all Car API access.
254     * @param context
255     * @param serviceConnectionListener listener for monitoring service connection.
256     * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that
257     *        service connection listener will be always in main thread regardless of this Looper.
258     * @return Car instance if system is in car environment and returns {@code null} otherwise.
259     */
260    public static Car createCar(Context context, ServiceConnection serviceConnectionListener,
261            @Nullable Looper looper) {
262        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
263            Log.e(CarLibLog.TAG_CAR, "FEATURE_AUTOMOTIVE not declared while android.car is used");
264            return null;
265        }
266        try {
267          return new Car(context, serviceConnectionListener, looper);
268        } catch (IllegalArgumentException e) {
269          // Expected when car service loader is not available.
270        }
271        return null;
272    }
273
274    /**
275     * A factory method that creates Car instance for all Car API access using main thread {@code
276     * Looper}.
277     *
278     * @see #createCar(Context, ServiceConnection, Looper)
279     */
280    public static Car createCar(Context context, ServiceConnection serviceConnectionListener) {
281      return createCar(context, serviceConnectionListener, null);
282    }
283
284    private Car(Context context, ServiceConnection serviceConnectionListener,
285            @Nullable Looper looper) {
286        mContext = context;
287        mServiceConnectionListenerClient = serviceConnectionListener;
288        if (looper == null) {
289            mLooper = Looper.getMainLooper();
290        } else {
291            mLooper = looper;
292        }
293        mEventHandler = new Handler(mLooper);
294    }
295
296    /**
297     * Car constructor when ICar binder is already available.
298     * @param context
299     * @param service
300     * @param looper
301     *
302     * @hide
303     */
304    public Car(Context context, ICar service, @Nullable Looper looper) {
305        mContext = context;
306        if (looper == null) {
307            mLooper = Looper.getMainLooper();
308        } else {
309            mLooper = looper;
310        }
311        mEventHandler = new Handler(mLooper);
312        mService = service;
313        mConnectionState = STATE_CONNECTED;
314        mServiceConnectionListenerClient = null;
315    }
316
317    /**
318     * Connect to car service. This can be called while it is disconnected.
319     * @throws IllegalStateException If connection is still on-going from previous
320     *         connect call or it is already connected
321     */
322    public void connect() throws IllegalStateException {
323        synchronized (this) {
324            if (mConnectionState != STATE_DISCONNECTED) {
325                throw new IllegalStateException("already connected or connecting");
326            }
327            mConnectionState = STATE_CONNECTING;
328            startCarService();
329        }
330    }
331
332    /**
333     * Disconnect from car service. This can be called while disconnected. Once disconnect is
334     * called, all Car*Managers from this instance becomes invalid, and
335     * {@link Car#getCarManager(String)} will return different instance if it is connected again.
336     */
337    public void disconnect() {
338        synchronized (this) {
339            if (mConnectionState == STATE_DISCONNECTED) {
340                return;
341            }
342            tearDownCarManagers();
343            mService = null;
344            mConnectionState = STATE_DISCONNECTED;
345            mContext.unbindService(mServiceConnectionListener);
346        }
347    }
348
349    /**
350     * Tells if it is connected to the service or not. This will return false if it is still
351     * connecting.
352     * @return
353     */
354    public boolean isConnected() {
355        synchronized (this) {
356            return mService != null;
357        }
358    }
359
360    /**
361     * Tells if this instance is already connecting to car service or not.
362     * @return
363     */
364    public boolean isConnecting() {
365        synchronized (this) {
366            return mConnectionState == STATE_CONNECTING;
367        }
368    }
369
370    /**
371     * Get car specific service as in {@link Context#getSystemService(String)}. Returned
372     * {@link Object} should be type-casted to the desired service.
373     * For example, to get sensor service,
374     * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);
375     * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}.
376     * @return Matching service manager or null if there is no such service.
377     * @throws CarNotConnectedException
378     */
379    public Object getCarManager(String serviceName) throws CarNotConnectedException {
380        CarManagerBase manager = null;
381        ICar service = getICarOrThrow();
382        synchronized (mCarManagerLock) {
383            manager = mServiceMap.get(serviceName);
384            if (manager == null) {
385                try {
386                    IBinder binder = service.getCarService(serviceName);
387                    if (binder == null) {
388                        Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" +
389                                serviceName);
390                        return null;
391                    }
392                    manager = createCarManager(serviceName, binder);
393                    if (manager == null) {
394                        Log.w(CarLibLog.TAG_CAR,
395                                "getCarManager could not create manager for service:" +
396                                serviceName);
397                        return null;
398                    }
399                    mServiceMap.put(serviceName, manager);
400                } catch (RemoteException e) {
401                    handleRemoteException(e);
402                }
403            }
404        }
405        return manager;
406    }
407
408    /**
409     * Return the type of currently connected car.
410     * @return
411     */
412    @ConnectionType
413    public int getCarConnectionType() {
414        return CONNECTION_TYPE_EMBEDDED;
415    }
416
417    /**
418     * IllegalStateException from XyzCarService with special message is re-thrown as a different
419     * exception. If the IllegalStateException is not understood then this message will throw the
420     * original exception.
421     *
422     * @param e exception from XyzCarService.
423     * @throws CarNotConnectedException
424     * @hide
425     */
426    public static void checkCarNotConnectedExceptionFromCarService(
427            IllegalStateException e) throws CarNotConnectedException, IllegalStateException {
428        String message = e.getMessage();
429        if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) {
430            throw new CarNotConnectedException();
431        } else {
432            throw e;
433        }
434    }
435
436    private CarManagerBase createCarManager(String serviceName, IBinder binder)
437            throws CarNotConnectedException {
438        CarManagerBase manager = null;
439        switch (serviceName) {
440            case AUDIO_SERVICE:
441                manager = new CarAudioManager(binder, mContext);
442                break;
443            case SENSOR_SERVICE:
444                manager = new CarSensorManager(binder, mContext, mLooper);
445                break;
446            case INFO_SERVICE:
447                manager = new CarInfoManager(binder);
448                break;
449            case APP_CONTEXT_SERVICE:
450                manager = new CarAppContextManager(binder, mLooper);
451                break;
452            case PACKAGE_SERVICE:
453                manager = new CarPackageManager(binder, mContext);
454                break;
455            case CAR_NAVIGATION_SERVICE:
456                manager = new CarNavigationManager(binder, mLooper);
457                break;
458            case CAMERA_SERVICE:
459                manager = new CarCameraManager(binder, mContext);
460                break;
461            case HVAC_SERVICE:
462                manager = new CarHvacManager(binder, mContext, mLooper);
463                break;
464            case PROJECTION_SERVICE:
465                manager = new CarProjectionManager(binder, mLooper);
466                break;
467            case RADIO_SERVICE:
468                manager = new CarRadioManager(binder, mLooper);
469                break;
470            case TEST_SERVICE:
471                /* CarTestManager exist in static library. So instead of constructing it here,
472                 * only pass binder wrapper so that CarTestManager can be constructed outside. */
473                manager = new CarTestManagerBinderWrapper(binder);
474                break;
475        }
476        return manager;
477    }
478
479    private void startCarService() {
480        Intent intent = new Intent();
481        intent.setPackage(CAR_SERVICE_PACKAGE);
482        intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
483        mContext.startService(intent);
484        mContext.bindService(intent, mServiceConnectionListener, 0);
485    }
486
487    private synchronized ICar getICarOrThrow() throws IllegalStateException {
488        if (mService == null) {
489            throw new IllegalStateException("not connected");
490        }
491        return mService;
492    }
493
494    private void handleRemoteException(RemoteException e) {
495        Log.w(CarLibLog.TAG_CAR, "RemoteException", e);
496        disconnect();
497    }
498
499    private void tearDownCarManagers() {
500        synchronized (mCarManagerLock) {
501            for (CarManagerBase manager: mServiceMap.values()) {
502                manager.onCarDisconnected();
503            }
504            mServiceMap.clear();
505        }
506    }
507}
508