• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.support.car;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.support.annotation.IntDef;
23 import android.support.annotation.NonNull;
24 import android.support.annotation.Nullable;
25 import android.support.car.content.pm.CarPackageManager;
26 import android.support.car.hardware.CarSensorManager;
27 import android.support.car.media.CarAudioManager;
28 import android.support.car.navigation.CarNavigationStatusManager;
29 import android.util.Log;
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.InvocationTargetException;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Map;
38 import java.util.Set;
39 
40 /**
41  * Top-level car API that provides access to all car services and data available in the platform.
42  * <p/>
43  * Use one of the createCar methods to create a new instance of the Car api.  The
44  * {@link CarConnectionCallback} will respond with an {@link CarConnectionCallback#onConnected(Car)}
45  * or {@link CarConnectionCallback#onDisconnected(Car)} message.  Nothing can be done with the
46  * car until onConnected is called.  When the car disconnects then reconnects you may still use
47  * the Car object but any manages retried from it should be considered invalid and will need to
48  * be retrieved.
49  *
50  * <p/>
51  * Once connected, {@link #getCarManager(String)} or {@link #getCarManager(Class)} can be used to
52  * retrieve a manager.  This is patterned after how one would retrieve a service from
53  * {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)}.  Once
54  * again if the car is disconnected you'll want to get new versions of these managers.
55  */
56 public class Car {
57 
58     private static final String TAG = "CAR.SUPPORT.LIB.CAR";
59     /**
60      * Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}.
61      */
62     public static final String SENSOR_SERVICE = "sensor";
63 
64     /**
65      * Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}.
66      */
67     public static final String INFO_SERVICE = "info";
68 
69     /**
70      * Service name for {@link CarAppFocusManager}.
71      */
72     public static final String APP_FOCUS_SERVICE = "app_focus";
73 
74     /**
75      * Service name for {@link CarPackageManager}.
76      * @hide
77      */
78     public static final String PACKAGE_SERVICE = "package";
79 
80     /**
81      * Service name for {@link CarAudioManager}.
82      */
83     public static final String AUDIO_SERVICE = "audio";
84     /**
85      * Service name for {@link CarNavigationStatusManager}.
86      * @hide
87      */
88     public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
89     /**
90      * Service name for {@link CarNavigationStatusManager}.
91      */
92     public static final String NAVIGATION_STATUS_SERVICE = "car_navigation_service";
93 
94     // TODO(jthol) move into a more robust registry implementation
95     private static final Map<Class, String> CLASS_TO_SERVICE_NAME;
96     static{
97         Map<Class, String> mapping = new HashMap<>();
mapping.put(CarSensorManager.class, SENSOR_SERVICE)98         mapping.put(CarSensorManager.class, SENSOR_SERVICE);
mapping.put(CarInfoManager.class, INFO_SERVICE)99         mapping.put(CarInfoManager.class, INFO_SERVICE);
mapping.put(CarAppFocusManager.class, APP_FOCUS_SERVICE)100         mapping.put(CarAppFocusManager.class, APP_FOCUS_SERVICE);
mapping.put(CarPackageManager.class, PACKAGE_SERVICE)101         mapping.put(CarPackageManager.class, PACKAGE_SERVICE);
mapping.put(CarAudioManager.class, AUDIO_SERVICE)102         mapping.put(CarAudioManager.class, AUDIO_SERVICE);
mapping.put(CarNavigationStatusManager.class, NAVIGATION_STATUS_SERVICE)103         mapping.put(CarNavigationStatusManager.class, NAVIGATION_STATUS_SERVICE);
104 
105         CLASS_TO_SERVICE_NAME = Collections.unmodifiableMap(mapping);
106     }
107 
108 
109     /**
110      * Type of car connection: car emulator, no physical connection.
111      * @hide
112      */
113     public static final int CONNECTION_TYPE_EMULATOR = 0;
114     /**
115      * Type of car connection: connected to a car via USB.
116      * @hide
117      */
118     public static final int CONNECTION_TYPE_USB = 1;
119     /**
120      * Type of car connection: connected to a car via Wi-Fi.
121      * @hide
122      */
123     public static final int CONNECTION_TYPE_WIFI = 2;
124     /**
125      * Type of car connection: on-device car emulator, for development (such as Local Head Unit).
126      * @hide
127      */
128     public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3;
129     /**
130      * Type of car connection: car emulator, connected over ADB (such as Desktop Head Unit).
131      * @hide
132      */
133     public static final int CONNECTION_TYPE_ADB_EMULATOR = 4;
134     /**
135      * Type of car connection: platform runs directly in car.
136      * @hide
137      */
138     public static final int CONNECTION_TYPE_EMBEDDED = 5;
139 
140     /**
141      * Unknown type (the support lib is likely out-of-date).
142      * @hide
143      */
144     public static final int CONNECTION_TYPE_UNKNOWN = -1;
145 
146     private static final Set<Integer> CONNECTION_TYPES = new HashSet<>();
147     static {
148         CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR);
149         CONNECTION_TYPES.add(CONNECTION_TYPE_USB);
150         CONNECTION_TYPES.add(CONNECTION_TYPE_WIFI);
151         CONNECTION_TYPES.add(CONNECTION_TYPE_ON_DEVICE_EMULATOR);
152         CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR);
153         CONNECTION_TYPES.add(CONNECTION_TYPE_EMBEDDED);
154     }
155 
156     /** @hide */
157     @IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI,
158             CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR,
159             CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_UNKNOWN})
160     @Retention(RetentionPolicy.SOURCE)
161     public @interface ConnectionType {
162     }
163 
164     /**
165      * Permission necessary to access car mileage information.
166      * @hide
167      */
168     public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
169     /**
170      * Permission necessary to access car fuel level.
171      * @hide
172      */
173     public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
174     /**
175      * Permission necessary to access car speed.
176      * @hide
177      */
178     public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
179     /**
180      * Permission necessary to access a car-specific communication channel.
181      */
182     public static final String PERMISSION_VENDOR_EXTENSION =
183             "android.car.permission.CAR_VENDOR_EXTENSION";
184     /**
185      * Permission necessary to use {@link android.car.navigation.CarNavigationStatusManager}.
186      */
187     public static final String PERMISSION_CAR_NAVIGATION_MANAGER =
188             "android.car.permission.PERMISSION_CAR_NAVIGATION_MANAGER";
189 
190 
191     /**
192      * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L.
193      */
194     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
195 
196     /**
197      * {@link CarServiceLoader} implementation for projected mode. Available only when the
198      * projected client library is linked.
199      */
200     private static final String PROJECTED_CAR_SERVICE_LOADER =
201             "com.google.android.apps.auto.sdk.service.CarServiceLoaderGms";
202     /**
203      * Permission necessary to change car audio volume through {@link CarAudioManager}.
204      * @hide
205      */
206     public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME =
207             "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
208 
209     private final Context mContext;
210     private final Handler mEventHandler;
211     private static final int STATE_DISCONNECTED = 0;
212     private static final int STATE_CONNECTING = 1;
213     private static final int STATE_CONNECTED = 2;
214     // @GuardedBy("this")
215     private int mConnectionState;
216 
217     private final CarServiceLoader.CarConnectionCallbackProxy mCarConnectionCallbackProxy =
218             new CarServiceLoader.CarConnectionCallbackProxy() {
219                 @Override
220                 public void onConnected() {
221                     synchronized (Car.this) {
222                         mConnectionState = STATE_CONNECTED;
223                     }
224                     mCarConnectionCallback.onConnected(Car.this);
225                 }
226 
227                 @Override
228                 public void onDisconnected() {
229                     synchronized (Car.this) {
230                         if (mConnectionState == STATE_DISCONNECTED) {
231                             return;
232                         }
233                         mConnectionState = STATE_DISCONNECTED;
234                     }
235                     mCarConnectionCallback.onDisconnected(Car.this);
236                 }
237             };
238 
239     private final CarConnectionCallback mCarConnectionCallback;
240     private final Object mCarManagerLock = new Object();
241     //@GuardedBy("mCarManagerLock")
242     private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
243     private final CarServiceLoader mCarServiceLoader;
244 
245 
246     /**
247      * A factory method that creates a Car instance with the given {@code Looper}.
248      *
249      * @param context The current app context.
250      * @param carConnectionCallback Receives information when the Car Service is started and
251      * stopped.
252      * @param handler The handler on which the callback should execute, or null to execute on the
253      * service's main thread. Note the service connection listener is always on the main
254      * thread regardless of the handler given.
255      * @return Car instance if system is in car environment; returns {@code null} otherwise.
256      */
createCar(Context context, CarConnectionCallback carConnectionCallback, @Nullable Handler handler)257     public static Car createCar(Context context,
258             CarConnectionCallback carConnectionCallback, @Nullable Handler handler) {
259         try {
260             return new Car(context, carConnectionCallback, handler);
261         } catch (IllegalArgumentException e) {
262             // Expected when Car Service loader is not available.
263             Log.w(TAG, "Car failed to be created", e);
264         }
265         return null;
266     }
267 
268     /**
269      * A factory method that creates Car instance using the main thread {@link Handler}.
270      *
271      * @see #createCar(Context, CarConnectionCallback, Handler)
272      */
createCar(Context context, CarConnectionCallback carConnectionCallback)273     public static Car createCar(Context context,
274             CarConnectionCallback carConnectionCallback) {
275         return createCar(context, carConnectionCallback, null);
276     }
277 
Car(Context context, CarConnectionCallback carConnectionCallback, @Nullable Handler handler)278     private Car(Context context, CarConnectionCallback carConnectionCallback,
279             @Nullable Handler handler) {
280         mContext = context;
281         mCarConnectionCallback = carConnectionCallback;
282         if (handler == null) {
283             Looper looper = Looper.getMainLooper();
284             handler = new Handler(looper);
285         }
286         mEventHandler = handler;
287 
288         if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
289             mCarServiceLoader =
290                     new CarServiceLoaderEmbedded(context, mCarConnectionCallbackProxy,
291                             mEventHandler);
292         } else {
293             mCarServiceLoader = loadCarServiceLoader(PROJECTED_CAR_SERVICE_LOADER, context,
294                     mCarConnectionCallbackProxy, mEventHandler);
295         }
296     }
297 
loadCarServiceLoader(String carServiceLoaderClassName, Context context, CarServiceLoader.CarConnectionCallbackProxy carConnectionCallbackProxy, Handler eventHandler)298     private CarServiceLoader loadCarServiceLoader(String carServiceLoaderClassName, Context context,
299             CarServiceLoader.CarConnectionCallbackProxy carConnectionCallbackProxy,
300             Handler eventHandler) throws IllegalArgumentException {
301         Class<? extends CarServiceLoader> carServiceLoaderClass = null;
302         try {
303             carServiceLoaderClass =
304                     Class.forName(carServiceLoaderClassName).asSubclass(CarServiceLoader.class);
305         } catch (ClassNotFoundException e) {
306             throw new IllegalArgumentException(
307                     "Cannot find CarServiceLoader implementation:" + carServiceLoaderClassName, e);
308         }
309         Constructor<? extends CarServiceLoader> ctor;
310         try {
311             ctor = carServiceLoaderClass.getDeclaredConstructor(Context.class,
312                     CarServiceLoader.CarConnectionCallbackProxy.class, Handler.class);
313         } catch (NoSuchMethodException e) {
314             throw new IllegalArgumentException("Cannot construct CarServiceLoader, no constructor: "
315                     + carServiceLoaderClassName, e);
316         }
317         try {
318             return ctor.newInstance(context, carConnectionCallbackProxy, eventHandler);
319         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
320                 | InvocationTargetException e) {
321             throw new IllegalArgumentException(
322                     "Cannot construct CarServiceLoader, constructor failed for "
323                             + carServiceLoaderClass.getName(), e);
324         }
325     }
326 
327     /**
328      * Car constructor when CarServiceLoader is already available.
329      *
330      * @param serviceLoader must be non-null and connected or {@link CarNotConnectedException} will
331      * be thrown.
332      * @hide
333      */
Car(@onNull CarServiceLoader serviceLoader)334     public Car(@NonNull CarServiceLoader serviceLoader) throws CarNotConnectedException {
335         if (!serviceLoader.isConnected()) {
336             throw new CarNotConnectedException();
337         }
338         mCarServiceLoader = serviceLoader;
339         mEventHandler = serviceLoader.getEventHandler();
340         mContext = serviceLoader.getContext();
341 
342         mConnectionState = STATE_CONNECTED;
343         mCarConnectionCallback = null;
344     }
345 
346     /**
347      * Connect to Car Service. Can be called while disconnected.
348      *
349      * @throws IllegalStateException if the car is connected or still trying to connect
350      * from previous calls.
351      */
connect()352     public void connect() throws IllegalStateException {
353         synchronized (this) {
354             if (mConnectionState != STATE_DISCONNECTED) {
355                 throw new IllegalStateException("already connected or connecting");
356             }
357             mConnectionState = STATE_CONNECTING;
358             mCarServiceLoader.connect();
359         }
360     }
361 
362     /**
363      * Disconnect from Car Service. Can be called while disconnected. After disconnect is
364      * called, all Car*Managers from this instance become invalid, and {@link
365      * Car#getCarManager(String)} returns a different instance if connected again.
366      */
disconnect()367     public void disconnect() {
368         synchronized (this) {
369             if (mConnectionState == STATE_DISCONNECTED) {
370                 return;
371             }
372             tearDownCarManagers();
373             mConnectionState = STATE_DISCONNECTED;
374             mCarServiceLoader.disconnect();
375         }
376     }
377 
378     /**
379      * @return Returns {@code true} if this object is connected to the service; {@code false}
380      * otherwise.
381      */
isConnected()382     public boolean isConnected() {
383         synchronized (this) {
384             return mConnectionState == STATE_CONNECTED;
385         }
386     }
387 
388     /**
389      * @return Returns {@code true} if this object is still connecting to the service.
390      */
isConnecting()391     public boolean isConnecting() {
392         synchronized (this) {
393             return mConnectionState == STATE_CONNECTING;
394         }
395     }
396 
397     /**
398      * Get a car-specific manager. This is modeled after {@link Context#getSystemService(String)}.
399      * The returned {@link Object} should be type cast to the desired manager. For example,
400      * to get the sensor service, use the following:
401      * <pre>{@code CarSensorManager sensorManager =
402      *     (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);}</pre>
403      *
404      * @param serviceName Name of service to create, for example {@link #SENSOR_SERVICE}.
405      * @return The requested service manager or null if the service is not available.
406      */
getCarManager(String serviceName)407     public Object getCarManager(String serviceName)
408             throws CarNotConnectedException {
409         Object manager = null;
410         synchronized (mCarManagerLock) {
411             manager = mServiceMap.get(serviceName);
412             if (manager == null) {
413                 manager = mCarServiceLoader.getCarManager(serviceName);
414             }
415             // do not store if it is not CarManagerBase. This can happen when system version
416             // is retrieved from this call.
417             if (manager != null && manager instanceof CarManagerBase) {
418                 mServiceMap.put(serviceName, (CarManagerBase) manager);
419             }
420         }
421         return manager;
422     }
423 
424     /**
425      * Get a car-specific manager. This is modeled after {@link Context#getSystemService(Class)}.
426      * The returned service will be type cast to the desired manager. For example,
427      * to get the sensor service, use the following:
428      * <pre>{@code CarSensorManager sensorManager = car.getCarManager(CarSensorManager.class);
429      * }</pre>
430      *
431      * @param serviceClass Class: The class of the desired service. For
432      * example {@link CarSensorManager}.
433      * @return The service or null if the class is not a supported car service.
434      */
getCarManager(Class<T> serviceClass)435     public <T> T getCarManager(Class<T> serviceClass) throws CarNotConnectedException {
436         // TODO(jthol) port to a more robust registry implementation
437         String serviceName = CLASS_TO_SERVICE_NAME.get(serviceClass);
438         return (serviceName == null) ? null : (T) getCarManager(serviceName);
439     }
440 
441     /**
442      * Return the type of currently connected car. This should only be used for testing scenarios
443      *
444      * @return One of {@link #CONNECTION_TYPE_USB}, {@link #CONNECTION_TYPE_WIFI},
445      * {@link #CONNECTION_TYPE_EMBEDDED}, {@link #CONNECTION_TYPE_ON_DEVICE_EMULATOR},
446      * {@link #CONNECTION_TYPE_ADB_EMULATOR},
447      * {@link #CONNECTION_TYPE_UNKNOWN}.
448      * @throws CarNotConnectedException if the connection to the car service has been lost.
449      * @hide
450      */
451     @ConnectionType
getCarConnectionType()452     public int getCarConnectionType() throws CarNotConnectedException {
453         int carConnectionType = mCarServiceLoader.getCarConnectionType();
454         if (!CONNECTION_TYPES.contains(carConnectionType)){
455             return CONNECTION_TYPE_UNKNOWN;
456         }
457         return carConnectionType;
458     }
459 
tearDownCarManagers()460     private void tearDownCarManagers() {
461         synchronized (mCarManagerLock) {
462             for (CarManagerBase manager : mServiceMap.values()) {
463                 manager.onCarDisconnected();
464             }
465             mServiceMap.clear();
466         }
467     }
468 }
469