• 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.support.annotation.IntDef;
20 import android.support.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.RemoteException;
28 import android.support.car.content.pm.CarPackageManager;
29 import android.support.car.hardware.CarSensorManager;
30 import android.support.car.navigation.CarNavigationManager;
31 import android.util.Log;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.lang.ref.WeakReference;
36 import java.lang.reflect.Constructor;
37 import java.lang.reflect.InvocationTargetException;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.LinkedList;
41 
42 /**
43  *   Top level car API.
44  *   This API works only for device with {@link PackageManager#FEATURE_AUTOMOTIVE} feature
45  *   supported or device with Google play service.
46  *   Calling this API with device with no such feature will lead into an exception.
47  *
48  */
49 public class Car {
50 
51     /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */
52     public static final String SENSOR_SERVICE = "sensor";
53 
54     /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */
55     public static final String INFO_SERVICE = "info";
56 
57     /** Service name for {@link CarAppContextManager}. */
58     public static final String APP_CONTEXT_SERVICE = "app_context";
59 
60     /** Service name for {@link CarPackageManager} */
61     public static final String PACKAGE_SERVICE = "package";
62 
63     /** Service name for {@link CarAudioManager} */
64     public static final String AUDIO_SERVICE = "audio";
65     /**
66      * Service name for {@link CarNavigationManager}
67      * @hide
68      */
69     public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
70 
71     /** Type of car connection: car emulator, not physical connection. */
72     public static final int CONNECTION_TYPE_EMULATOR        = 0;
73     /** Type of car connection: connected to a car via USB. */
74     public static final int CONNECTION_TYPE_USB             = 1;
75     /** Type of car connection: connected to a car via WIFI. */
76     public static final int CONNECTION_TYPE_WIFI            = 2;
77     /** Type of car connection: on-device car emulator, for development (e.g. Local Head Unit). */
78     public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3;
79     /** Type of car connection: car emulator, connected over ADB (e.g. Desktop Head Unit). */
80     public static final int CONNECTION_TYPE_ADB_EMULATOR = 4;
81     /** Type of car connection: platform runs directly in car. */
82     public static final int CONNECTION_TYPE_EMBEDDED = 5;
83     /**
84      * Type of car connection: platform runs directly in car but with mocked vehicle hal.
85      * This will only happen in testing environment.
86      * @hide
87      */
88     public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6;
89 
90     /** @hide */
91     @IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI,
92         CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR, CONNECTION_TYPE_EMBEDDED})
93     @Retention(RetentionPolicy.SOURCE)
94     public @interface ConnectionType {}
95 
96     /** Permission necessary to access car's mileage information. */
97     public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
98     /** Permission necessary to access car's fuel level. */
99     public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
100     /** Permission necessary to access car's speed. */
101     public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
102     /** Permission necessary to access car specific communication channel. */
103     public static final String PERMISSION_VENDOR_EXTENSION =
104             "android.car.permission.CAR_VENDOR_EXTENSION";
105     /**
106      * Permission necessary to use {@link android.car.navigation.CarNavigationManager}.
107      * @hide
108      */
109     public static final String PERMISSION_CAR_NAVIGATION_MANAGER =
110             "android.car.permission.PERMISSION_CAR_NAVIGATION_MANAGER";
111 
112 
113     /**
114      * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L.
115      * @hide
116      */
117     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
118 
119     /**
120      * {@link CarServiceLoader} implementation for projected mode. Only available when projected
121      * client library is linked.
122      * @hide
123      */
124     private static final String PROJECTED_CAR_SERVICE_LOADER =
125             "com.google.android.gms.car.CarServiceLoaderGms";
126 
127     /**
128      * CarXyzService throws IllegalStateException with this message is re-thrown as
129      * {@link CarNotConnectedException}.
130      *
131      * @hide
132      */
133     public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected";
134 
135     /** @hide */
136     public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar";
137 
138     private final Context mContext;
139     private final Looper mLooper;
140     private static final int STATE_DISCONNECTED = 0;
141     private static final int STATE_CONNECTING = 1;
142     private static final int STATE_CONNECTED = 2;
143     // @GuardedBy("this")
144     private int mConnectionState;
145 
146     private final ServiceConnectionListener mServiceConnectionListener =
147             new ServiceConnectionListener () {
148         public void onServiceConnected(ComponentName name) {
149             synchronized (Car.this) {
150                 mConnectionState = STATE_CONNECTED;
151             }
152             mServiceConnectionListenerClient.onServiceConnected(name);
153         }
154 
155         public void onServiceDisconnected(ComponentName name) {
156             synchronized (Car.this) {
157                 if (mConnectionState  == STATE_DISCONNECTED) {
158                     return;
159                 }
160                 mConnectionState = STATE_DISCONNECTED;
161             }
162             mServiceConnectionListenerClient.onServiceDisconnected(name);
163             connect();
164         }
165 
166         public void onServiceSuspended(int cause) {
167             mServiceConnectionListenerClient.onServiceSuspended(cause);
168         }
169 
170         public void onServiceConnectionFailed(int cause) {
171             mServiceConnectionListenerClient.onServiceConnectionFailed(cause);
172         }
173     };
174 
175     private final ServiceConnectionListener mServiceConnectionListenerClient;
176     private final Object mCarManagerLock = new Object();
177     //@GuardedBy("mCarManagerLock")
178     private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
179     private final CarServiceLoader mCarServiceLoader;
180 
181     /** Handler for generic event dispatching. */
182     private final Handler mEventHandler;
183 
184     /**
185      * A factory method that creates Car instance for all Car API access.
186      * @param context
187      * @param serviceConnectionListener listener for monitoring service connection.
188      * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that
189      *        service connection listener will be always in main thread regardless of this Looper.
190      * @return Car instance if system is in car environment and returns {@code null} otherwise.
191      */
createCar(Context context, ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper)192     public static Car createCar(Context context,
193             ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper) {
194         try {
195           return new Car(context, serviceConnectionListener, looper);
196         } catch (IllegalArgumentException e) {
197           // Expected when car service loader is not available.
198         }
199         return null;
200     }
201 
202     /**
203      * A factory method that creates Car instance for all Car API access using main thread {@code
204      * Looper}.
205      *
206      * @see #createCar(Context, ServiceConnectionListener, Looper)
207      */
createCar(Context context, ServiceConnectionListener serviceConnectionListener)208     public static Car createCar(Context context,
209             ServiceConnectionListener serviceConnectionListener) {
210       return createCar(context, serviceConnectionListener, null);
211     }
212 
Car(Context context, ServiceConnectionListener serviceConnectionListener, @Nullable Looper looper)213     private Car(Context context, ServiceConnectionListener serviceConnectionListener,
214             @Nullable Looper looper) {
215         mContext = context;
216         mServiceConnectionListenerClient = serviceConnectionListener;
217         if (looper == null) {
218             mLooper = Looper.getMainLooper();
219         } else {
220             mLooper = looper;
221         }
222         mEventHandler = new Handler(mLooper);
223         if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
224             mCarServiceLoader = new CarServiceLoaderEmbedded(context, mServiceConnectionListener,
225                     mLooper);
226         } else {
227             mCarServiceLoader = loadCarServiceLoader(PROJECTED_CAR_SERVICE_LOADER, context,
228                     mServiceConnectionListener, mLooper);
229         }
230     }
231 
loadCarServiceLoader(String carServiceLoaderClassName, Context context, ServiceConnectionListener serviceConnectionListener, Looper looper)232     private CarServiceLoader loadCarServiceLoader(String carServiceLoaderClassName,
233             Context context, ServiceConnectionListener serviceConnectionListener, Looper looper)
234                     throws IllegalArgumentException {
235         Class carServiceLoaderClass = null;
236         try {
237             carServiceLoaderClass = Class.forName(carServiceLoaderClassName);
238         } catch (ClassNotFoundException e) {
239             throw new IllegalArgumentException("Cannot find CarServiceLoader implementation:" +
240                     carServiceLoaderClassName, e);
241         }
242         Constructor<?> ctor;
243         try {
244             ctor = carServiceLoaderClass.getDeclaredConstructor(Context.class,
245                     ServiceConnectionListener.class, Looper.class);
246         } catch (NoSuchMethodException e) {
247             throw new IllegalArgumentException("Cannot construct CarServiceLoader, no constructor: "
248                     + carServiceLoaderClassName, e);
249         }
250         try {
251             return (CarServiceLoader) ctor.newInstance(context,
252                     serviceConnectionListener, looper);
253         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
254                 | InvocationTargetException e) {
255             throw new IllegalArgumentException(
256                     "Cannot construct CarServiceLoader, constructor failed for "
257                     + carServiceLoaderClass.getName(), e);
258         }
259     }
260 
261     /**
262      * Car constructor when CarServiceLoader is already available.
263      * @param context
264      * @param serviceLoader
265      * @param looper
266      *
267      * @hide
268      */
Car(Context context, CarServiceLoader serviceLoader, @Nullable Looper looper)269     public Car(Context context, CarServiceLoader serviceLoader, @Nullable Looper looper) {
270         mContext = context;
271         if (looper == null) {
272             mLooper = Looper.getMainLooper();
273         } else {
274             mLooper = looper;
275         }
276         mEventHandler = new Handler(mLooper);
277         mConnectionState = STATE_CONNECTED;
278         mCarServiceLoader = serviceLoader;
279         mServiceConnectionListenerClient = null;
280     }
281 
282     /**
283      * Connect to car service. This can be called while it is disconnected.
284      * @throws IllegalStateException If connection is still on-going from previous
285      *         connect call or it is already connected
286      */
connect()287     public void connect() throws IllegalStateException {
288         synchronized (this) {
289             if (mConnectionState != STATE_DISCONNECTED) {
290                 throw new IllegalStateException("already connected or connecting");
291             }
292             mConnectionState = STATE_CONNECTING;
293             mCarServiceLoader.connect();
294         }
295     }
296 
297     /**
298      * Disconnect from car service. This can be called while disconnected. Once disconnect is
299      * called, all Car*Managers from this instance becomes invalid, and
300      * {@link Car#getCarManager(String)} will return different instance if it is connected again.
301      */
disconnect()302     public void disconnect() {
303         synchronized (this) {
304             if (mConnectionState == STATE_DISCONNECTED) {
305                 return;
306             }
307             tearDownCarManagers();
308             mConnectionState = STATE_DISCONNECTED;
309             mCarServiceLoader.disconnect();
310         }
311     }
312 
313     /**
314      * Tells if it is connected to the service or not. This will return false if it is still
315      * connecting.
316      * @return
317      */
isConnected()318     public boolean isConnected() {
319         synchronized (this) {
320             return mConnectionState == STATE_CONNECTED;
321         }
322     }
323 
324     /**
325      * Tells if this instance is already connecting to car service or not.
326      * @return
327      */
isConnecting()328     public boolean isConnecting() {
329         synchronized (this) {
330             return mConnectionState == STATE_CONNECTING;
331         }
332     }
333 
334     /**
335      * Tells if car is connected to car or not. In some car environments, being connected to service
336      * does not necessarily mean being connected to car.
337      */
isConnectedToCar()338     public boolean isConnectedToCar() {
339         return mCarServiceLoader.isConnectedToCar();
340     }
341 
342     /**
343      * Get car specific service as in {@link Context#getSystemService(String)}. Returned
344      * {@link Object} should be type-casted to the desired service.
345      * For example, to get sensor service,
346      * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);
347      * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}.
348      * @return Matching service manager or null if there is no such service.
349      */
getCarManager(String serviceName)350     public Object getCarManager(String serviceName)
351             throws CarNotSupportedException, CarNotConnectedException {
352         Object manager = null;
353         synchronized (mCarManagerLock) {
354             manager = mServiceMap.get(serviceName);
355             if (manager == null) {
356                 manager = mCarServiceLoader.getCarManager(serviceName);
357             }
358             // do not store if it is not CarManagerBase. This can happen when system version
359             // is retrieved from this call.
360             if (manager != null && manager instanceof CarManagerBase) {
361                 mServiceMap.put(serviceName, (CarManagerBase) manager);
362             }
363         }
364         return manager;
365     }
366 
367     /**
368      * Return the type of currently connected car.
369      * @return
370      * @throws CarNotConnectedException
371      */
372     @ConnectionType
getCarConnectionType()373     public int getCarConnectionType() throws CarNotConnectedException {
374         return mCarServiceLoader.getCarConnectionType();
375     }
376 
377     /**
378      * Registers a {@link CarConnectionListener}.
379      *
380      * Avoid reregistering unregistered listeners. If an unregistered listener is reregistered,
381      * it may receive duplicate calls to {@link CarConnectionListener#onConnected}.
382      *
383      * @throws IllegalStateException if service is not connected.
384      */
registerCarConnectionListener(CarConnectionListener listener)385     public void registerCarConnectionListener(CarConnectionListener listener)
386             throws IllegalStateException, CarNotConnectedException {
387         assertCarConnection();
388         mCarServiceLoader.registerCarConnectionListener(listener);
389     }
390 
391     /**
392      * Unregisters a {@link CarConnectionListener}.
393      *
394      * <b>Note:</b> If this method is called from a thread besides the client's looper thread,
395      * there is no guarantee that the unregistered listener will not receive callbacks after
396      * this method returns.
397      */
unregisterCarConnectionListener(CarConnectionListener listener)398     public void unregisterCarConnectionListener(CarConnectionListener listener) {
399         mCarServiceLoader.unregisterCarConnectionListener(listener);
400     }
401 
402     /**
403      * IllegalStateException from XyzCarService with special message is re-thrown as a different
404      * exception. If the IllegalStateException is not understood then this message will throw the
405      * original exception.
406      *
407      * @param e exception from XyzCarService.
408      * @throws CarNotConnectedException
409      * @hide
410      */
checkCarNotConnectedExceptionFromCarService( IllegalStateException e)411     public static void checkCarNotConnectedExceptionFromCarService(
412             IllegalStateException e) throws CarNotConnectedException, IllegalStateException {
413         String message = e.getMessage();
414         if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) {
415             throw new CarNotConnectedException();
416         } else {
417             throw e;
418         }
419     }
420 
assertCarConnection()421     private synchronized void assertCarConnection() throws IllegalStateException {
422         if (!mCarServiceLoader.isConnectedToCar()) {
423             throw new IllegalStateException("not connected");
424         }
425     }
426 
tearDownCarManagers()427     private void tearDownCarManagers() {
428         synchronized (mCarManagerLock) {
429             for (CarManagerBase manager: mServiceMap.values()) {
430                 manager.onCarDisconnected();
431             }
432             mServiceMap.clear();
433         }
434     }
435 }
436