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