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