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