blob: 63e1b873c26e8ec30abc5758c9404bde9078bc55 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.car;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.car.content.pm.CarPackageManager;
import android.support.car.hardware.CarSensorManager;
import android.support.car.media.CarAudioManager;
import android.support.car.navigation.CarNavigationStatusManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Top-level car API that provides access to all car services and data available in the platform.
* Developers may create their own instance of {@link Car} when using a CarActivity.
*/
public class Car {
/**
* Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}.
*/
public static final String SENSOR_SERVICE = "sensor";
/**
* Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}.
*/
public static final String INFO_SERVICE = "info";
/**
* Service name for {@link CarAppFocusManager}.
*/
public static final String APP_FOCUS_SERVICE = "app_focus";
/**
* Service name for {@link CarPackageManager}.
*/
public static final String PACKAGE_SERVICE = "package";
/**
* Service name for {@link CarAudioManager}.
*/
public static final String AUDIO_SERVICE = "audio";
/**
* Service name for {@link CarNavigationStatusManager}.
* @hide
*/
public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service";
/**
* Service name for {@link CarNavigationStatusManager}.
*/
public static final String NAVIGATION_STATUS_SERVICE = "car_navigation_service";
// TODO(jthol) move into a more robust registry implementation
private static final Map<Class, String> CLASS_TO_SERVICE_NAME;
static{
Map<Class, String> mapping = new HashMap<>();
mapping.put(CarSensorManager.class, SENSOR_SERVICE);
mapping.put(CarInfoManager.class, INFO_SERVICE);
mapping.put(CarAppFocusManager.class, APP_FOCUS_SERVICE);
mapping.put(CarPackageManager.class, PACKAGE_SERVICE);
mapping.put(CarAudioManager.class, AUDIO_SERVICE);
mapping.put(CarNavigationStatusManager.class, NAVIGATION_STATUS_SERVICE);
CLASS_TO_SERVICE_NAME = Collections.unmodifiableMap(mapping);
}
/**
* Type of car connection: car emulator, no physical connection.
*/
public static final int CONNECTION_TYPE_EMULATOR = 0;
/**
* Type of car connection: connected to a car via USB.
*/
public static final int CONNECTION_TYPE_USB = 1;
/**
* Type of car connection: connected to a car via Wi-Fi.
*/
public static final int CONNECTION_TYPE_WIFI = 2;
/**
* Type of car connection: on-device car emulator, for development (such as Local Head Unit).
*/
public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3;
/**
* Type of car connection: car emulator, connected over ADB (such as Desktop Head Unit).
*/
public static final int CONNECTION_TYPE_ADB_EMULATOR = 4;
/**
* Type of car connection: platform runs directly in car.
*/
public static final int CONNECTION_TYPE_EMBEDDED = 5;
/**
* Type of car connection: platform runs directly in car with mocked vehicle HAL. Occurs
* only in a testing environment.
*/
public static final int CONNECTION_TYPE_EMBEDDED_MOCKING = 6;
/**
* Unknown type (the support lib is likely out-of-date).
*/
public static final int CONNECTION_TYPE_UNKNOWN = -1;
private static final Set<Integer> CONNECTION_TYPES = new HashSet<>();
static {
CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR);
CONNECTION_TYPES.add(CONNECTION_TYPE_USB);
CONNECTION_TYPES.add(CONNECTION_TYPE_WIFI);
CONNECTION_TYPES.add(CONNECTION_TYPE_ON_DEVICE_EMULATOR);
CONNECTION_TYPES.add(CONNECTION_TYPE_ADB_EMULATOR);
CONNECTION_TYPES.add(CONNECTION_TYPE_EMBEDDED);
CONNECTION_TYPES.add(CONNECTION_TYPE_EMBEDDED_MOCKING);
}
/** @hide */
@IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI,
CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR,
CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_UNKNOWN})
@Retention(RetentionPolicy.SOURCE)
public @interface ConnectionType {
}
/**
* Permission necessary to access car mileage information.
* @hide
*/
public static final String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
/**
* Permission necessary to access car fuel level.
* @hide
*/
public static final String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
/**
* Permission necessary to access car speed.
* @hide
*/
public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
/**
* Permission necessary to access a car-specific communication channel.
*/
public static final String PERMISSION_VENDOR_EXTENSION =
"android.car.permission.CAR_VENDOR_EXTENSION";
/**
* Permission necessary to use {@link android.car.navigation.CarNavigationStatusManager}.
*/
public static final String PERMISSION_CAR_NAVIGATION_MANAGER =
"android.car.permission.PERMISSION_CAR_NAVIGATION_MANAGER";
/**
* PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L.
*/
private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
/**
* {@link CarServiceLoader} implementation for projected mode. Available only when the
* projected client library is linked.
*/
private static final String PROJECTED_CAR_SERVICE_LOADER =
"com.google.android.gms.car.CarServiceLoaderGms";
/**
* Permission necessary to change car audio volume through {@link CarAudioManager}.
* @hide
*/
public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME =
"android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
private final Context mContext;
private final Handler mEventHandler;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
// @GuardedBy("this")
private int mConnectionState;
private final ServiceConnectionCallback mServiceConnectionCallback =
new ServiceConnectionCallback() {
@Override
public void onServiceConnected() {
synchronized (Car.this) {
mConnectionState = STATE_CONNECTED;
}
mServiceConnectionCallbackClient.onServiceConnected();
}
@Override
public void onServiceDisconnected() {
synchronized (Car.this) {
if (mConnectionState == STATE_DISCONNECTED) {
return;
}
mConnectionState = STATE_DISCONNECTED;
}
mServiceConnectionCallbackClient.onServiceDisconnected();
connect();
}
@Override
public void onServiceConnectionFailed() {
mServiceConnectionCallbackClient.onServiceConnectionFailed();
}
};
private final ServiceConnectionCallback mServiceConnectionCallbackClient;
private final Object mCarManagerLock = new Object();
//@GuardedBy("mCarManagerLock")
private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
private final CarServiceLoader mCarServiceLoader;
/**
* A factory method that creates a Car instance with the given {@code Looper}.
*
* @param context The current app context.
* @param serviceConnectionCallback Receives information when the Car Service is started and
* stopped.
* @param handler The handler on which the callback should execute, or null to execute on the
* service's main thread. Note the service connection listener is always on the main
* thread regardless of the handler given.
* @return Car instance if system is in car environment; returns {@code null} otherwise.
*/
public static Car createCar(Context context,
ServiceConnectionCallback serviceConnectionCallback, @Nullable Handler handler) {
try {
return new Car(context, serviceConnectionCallback, handler);
} catch (IllegalArgumentException e) {
// Expected when Car Service loader is not available.
}
return null;
}
/**
* A factory method that creates Car instance using the main thread {@link Handler}.
*
* @see #createCar(Context, ServiceConnectionCallback, Handler)
*/
public static Car createCar(Context context,
ServiceConnectionCallback serviceConnectionCallback) {
return createCar(context, serviceConnectionCallback, null);
}
private Car(Context context, ServiceConnectionCallback serviceConnectionCallback,
@Nullable Handler handler) {
mContext = context;
mServiceConnectionCallbackClient = serviceConnectionCallback;
if (handler == null) {
Looper looper = Looper.getMainLooper();
handler = new Handler(looper);
}
mEventHandler = handler;
if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
mCarServiceLoader =
new CarServiceLoaderEmbedded(this, context, mServiceConnectionCallback,
mEventHandler);
} else {
mCarServiceLoader = loadCarServiceLoader(PROJECTED_CAR_SERVICE_LOADER, context,
mServiceConnectionCallback, mEventHandler);
}
}
private CarServiceLoader loadCarServiceLoader(String carServiceLoaderClassName, Context context,
ServiceConnectionCallback serviceConnectionCallback, Handler eventHandler)
throws IllegalArgumentException {
Class<? extends CarServiceLoader> carServiceLoaderClass = null;
try {
carServiceLoaderClass =
Class.forName(carServiceLoaderClassName).asSubclass(CarServiceLoader.class);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
"Cannot find CarServiceLoader implementation:" + carServiceLoaderClassName, e);
}
Constructor<? extends CarServiceLoader> ctor;
try {
ctor = carServiceLoaderClass
.getDeclaredConstructor(Context.class, ServiceConnectionCallback.class,
Looper.class);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Cannot construct CarServiceLoader, no constructor: "
+ carServiceLoaderClassName, e);
}
try {
return ctor.newInstance(context, serviceConnectionCallback, eventHandler);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalArgumentException(
"Cannot construct CarServiceLoader, constructor failed for "
+ carServiceLoaderClass.getName(), e);
}
}
/**
* Car constructor when CarServiceLoader is already available.
*
* @param serviceLoader must be non-null and connected or {@link CarNotConnectedException} will
* be thrown.
* @hide
*/
public Car(@NonNull CarServiceLoader serviceLoader) throws CarNotConnectedException {
if (!serviceLoader.isConnectedToCar()) {
throw new CarNotConnectedException();
}
mCarServiceLoader = serviceLoader;
mEventHandler = serviceLoader.getEventHandler();
mContext = serviceLoader.getContext();
mConnectionState = STATE_CONNECTED;
mServiceConnectionCallbackClient = null;
}
/**
* Connect to Car Service. Can be called while disconnected.
*
* @throws IllegalStateException if the car is connected or still trying to connect
* from previous calls.
*/
public void connect() throws IllegalStateException {
synchronized (this) {
if (mConnectionState != STATE_DISCONNECTED) {
throw new IllegalStateException("already connected or connecting");
}
mConnectionState = STATE_CONNECTING;
mCarServiceLoader.connect();
}
}
/**
* Disconnect from Car Service. Can be called while disconnected. After disconnect is
* called, all Car*Managers from this instance become invalid, and {@link
* Car#getCarManager(String)} returns a different instance if connected again.
*/
public void disconnect() {
synchronized (this) {
if (mConnectionState == STATE_DISCONNECTED) {
return;
}
tearDownCarManagers();
mConnectionState = STATE_DISCONNECTED;
mCarServiceLoader.disconnect();
}
}
/**
* @return Returns {@code true} if this object is connected to the service; {@code false}
* otherwise.
*/
public boolean isConnected() {
synchronized (this) {
return mConnectionState == STATE_CONNECTED;
}
}
/**
* @return Returns {@code true} if this object is still connecting to the service.
*/
public boolean isConnecting() {
synchronized (this) {
return mConnectionState == STATE_CONNECTING;
}
}
/**
* @return Returns {@code true} if car is connected to the Car Service. In some car
* environments, being connected to the service does not equate to being connected to a car.
*/
public boolean isConnectedToCar() {
return mCarServiceLoader.isConnectedToCar();
}
/**
* Get a car-specific manager. This is modeled after {@link Context#getSystemService(String)}.
* The returned {@link Object} should be type cast to the desired manager. For example,
* to get the sensor service, use the following:
* <pre>{@code CarSensorManager sensorManager =
* (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);}</pre>
*
* @param serviceName Name of service to create, for example {@link #SENSOR_SERVICE}.
* @return The requested service manager or null if the service is not available.
*/
public Object getCarManager(String serviceName)
throws CarNotConnectedException {
Object manager = null;
synchronized (mCarManagerLock) {
manager = mServiceMap.get(serviceName);
if (manager == null) {
manager = mCarServiceLoader.getCarManager(serviceName);
}
// do not store if it is not CarManagerBase. This can happen when system version
// is retrieved from this call.
if (manager != null && manager instanceof CarManagerBase) {
mServiceMap.put(serviceName, (CarManagerBase) manager);
}
}
return manager;
}
/**
* Get a car-specific manager. This is modeled after {@link Context#getSystemService(Class)}.
* The returned service will be type cast to the desired manager. For example,
* to get the sensor service, use the following:
* <pre>{@code CarSensorManager sensorManager = car.getCarManager(CarSensorManager.class);
* }</pre>
*
* @param serviceClass Class: The class of the desired service. For
* example {@link CarSensorManager}.
* @return The service or null if the class is not a supported car service.
*/
public <T> T getCarManager(Class<T> serviceClass) throws CarNotConnectedException {
// TODO(jthol) port to a more robust registry implementation
String serviceName = CLASS_TO_SERVICE_NAME.get(serviceClass);
return (serviceName == null) ? null : (T) getCarManager(serviceName);
}
/**
* Return the type of currently connected car. This should only be used for testing scenarios
*
* @return One of {@link #CONNECTION_TYPE_USB}, {@link #CONNECTION_TYPE_WIFI},
* {@link #CONNECTION_TYPE_EMBEDDED}, {@link #CONNECTION_TYPE_ON_DEVICE_EMULATOR},
* {@link #CONNECTION_TYPE_ADB_EMULATOR}, {@link #CONNECTION_TYPE_EMBEDDED_MOCKING},
* {@link #CONNECTION_TYPE_UNKNOWN}.
* @throws CarNotConnectedException if the connection to the car service has been lost.
* @hide
*/
@ConnectionType
public int getCarConnectionType() throws CarNotConnectedException {
int carConnectionType = mCarServiceLoader.getCarConnectionType();
if (!CONNECTION_TYPES.contains(carConnectionType)){
return CONNECTION_TYPE_UNKNOWN;
}
return carConnectionType;
}
/**
* Register a {@link CarConnectionCallback}. Avoid reregistering callbacks. If a callback is
* reregistered, it may receive duplicate calls to {@link CarConnectionCallback#onConnected}.
* @param listener The listener to register.
*
* @throws IllegalStateException if service is not connected.
*/
public void registerCarConnectionCallback(CarConnectionCallback listener)
throws IllegalStateException, CarNotConnectedException {
assertCarConnection();
mCarServiceLoader.registerCarConnectionCallback(listener);
}
/**
* Unregister a {@link CarConnectionCallback}. When this method is called from a
* thread other than the client's looper thread, there is no guarantee the unregistered
* listener will not receive callbacks after this method returns.
* @param listener The listener to unregister.
*/
public void unregisterCarConnectionCallback(CarConnectionCallback listener) {
mCarServiceLoader.unregisterCarConnectionCallback(listener);
}
private synchronized void assertCarConnection() throws IllegalStateException {
if (!mCarServiceLoader.isConnectedToCar()) {
throw new IllegalStateException("not connected");
}
}
private void tearDownCarManagers() {
synchronized (mCarManagerLock) {
for (CarManagerBase manager : mServiceMap.values()) {
manager.onCarDisconnected();
}
mServiceMap.clear();
}
}
}