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