aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkeunyoung <keunyoung@google.com>2015-07-10 12:21:47 -0700
committerRakesh Iyer <rni@google.com>2015-11-17 15:04:55 -0800
commitca515079e9fc0c35b1498830f67378e9ccf949e5 (patch)
tree39423bf6966344bfeaf4521f265b48d59909f1d5
parenta20c9699d0d55f6ffa704ce577e5cf489e9307c9 (diff)
downloadCar-ca515079e9fc0c35b1498830f67378e9ccf949e5.tar.gz
add skeleton for car service and car api
- car service: system uid with system signature, starts in PRE_BOOT_COMPLETED - added minimal skeleton implementation for proof of convept for several key ideas: Car api, CarServiceLoader interface, CarActivity abstraction - Also adding CarSensorManager/Service for defining flow in HAL initialization. bug: 22701368, 22702215 Change-Id: If664bbd7b939102b7ea48bdde61ec068c42582cd (cherry picked from commit d58724adeb671998c511995e177874a3eea025df)
-rw-r--r--.gitignore3
-rw-r--r--Android.mk20
-rw-r--r--carsupport-lib/Android.mk26
-rw-r--r--carsupport-lib/src/android/support/car/Car.java378
-rw-r--r--carsupport-lib/src/android/support/car/CarApiUtil.java80
-rw-r--r--carsupport-lib/src/android/support/car/CarInfo.aidl19
-rw-r--r--carsupport-lib/src/android/support/car/CarInfo.java50
-rw-r--r--carsupport-lib/src/android/support/car/CarLibLog.java22
-rw-r--r--carsupport-lib/src/android/support/car/CarManagerBase.java24
-rw-r--r--carsupport-lib/src/android/support/car/CarNotConnectedException.java40
-rw-r--r--carsupport-lib/src/android/support/car/CarNotSupportedException.java40
-rw-r--r--carsupport-lib/src/android/support/car/CarSensorEvent.aidl19
-rw-r--r--carsupport-lib/src/android/support/car/CarSensorEvent.java806
-rw-r--r--carsupport-lib/src/android/support/car/CarSensorManager.java482
-rw-r--r--carsupport-lib/src/android/support/car/CarServiceLoader.java59
-rw-r--r--carsupport-lib/src/android/support/car/CarUiInfo.aidl19
-rw-r--r--carsupport-lib/src/android/support/car/CarUiInfo.java50
-rw-r--r--carsupport-lib/src/android/support/car/DefaultCarServiceLoader.java69
-rw-r--r--carsupport-lib/src/android/support/car/ICar.aidl41
-rw-r--r--carsupport-lib/src/android/support/car/ICarConnectionListener.aidl26
-rw-r--r--carsupport-lib/src/android/support/car/ICarSensor.aidl48
-rw-r--r--carsupport-lib/src/android/support/car/ICarSensorEventListener.aidl28
-rw-r--r--carsupport-lib/src/android/support/car/ServiceConnectionListener.java32
-rw-r--r--carsupport-lib/src/android/support/car/app/CarActivity.java166
-rw-r--r--carsupport-lib/src/android/support/car/app/CarProxyActivity.java190
-rw-r--r--service/Android.mk37
-rw-r--r--service/AndroidManifest.xml48
-rw-r--r--service/proguard.flags3
-rw-r--r--service/src/com/android/car/BootReceiver.java43
-rw-r--r--service/src/com/android/car/CarLog.java23
-rw-r--r--service/src/com/android/car/CarSensorService.java833
-rw-r--r--service/src/com/android/car/CarService.java61
-rw-r--r--service/src/com/android/car/CarServiceBase.java36
-rw-r--r--service/src/com/android/car/CarServiceUtils.java22
-rw-r--r--service/src/com/android/car/DayNightModePolicy.java96
-rw-r--r--service/src/com/android/car/DrivingStatePolicy.java207
-rw-r--r--service/src/com/android/car/ICarImpl.java128
-rw-r--r--service/src/com/android/car/hal/Hal.java60
-rw-r--r--service/src/com/android/car/hal/SensorHal.java75
-rw-r--r--service/src/com/android/car/hal/SensorHalBase.java54
-rw-r--r--tests/Android.mk20
-rw-r--r--tests/api_test/Android.mk33
-rw-r--r--tests/api_test/AndroidManifest.xml28
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/CarActivityTest.java90
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/CarSensorManagerTest.java106
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/CarTest.java105
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/TestAction.java91
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/TestCarActivity.java102
-rw-r--r--tests/api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java26
-rw-r--r--tests/car_activity_test_app/Android.mk29
-rw-r--r--tests/car_activity_test_app/AndroidManifest.xml31
-rw-r--r--tests/car_activity_test_app/res/drawable-hdpi/ic_launcher.pngbin0 -> 7658 bytes
-rw-r--r--tests/car_activity_test_app/res/drawable-mdpi/ic_launcher.pngbin0 -> 3777 bytes
-rw-r--r--tests/car_activity_test_app/res/drawable-xhdpi/ic_launcher.pngbin0 -> 12516 bytes
-rw-r--r--tests/car_activity_test_app/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 24777 bytes
-rw-r--r--tests/car_activity_test_app/res/layout/hello_caractivity.xml20
-rw-r--r--tests/car_activity_test_app/res/values/strings.xml5
-rw-r--r--tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarActivity.java95
-rw-r--r--tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarProxyActivity.java26
59 files changed, 5270 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..18b96fac76
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*~
+.project
+.classpath
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000000..d1148317ec
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/carsupport-lib/Android.mk b/carsupport-lib/Android.mk
new file mode 100644
index 0000000000..5ae46b0426
--- /dev/null
+++ b/carsupport-lib/Android.mk
@@ -0,0 +1,26 @@
+# 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libcarsupport
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/carsupport-lib/src/android/support/car/Car.java b/carsupport-lib/src/android/support/car/Car.java
new file mode 100644
index 0000000000..a0917f77b3
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/Car.java
@@ -0,0 +1,378 @@
+/*
+ * 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.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+
+/**
+ * Top level car API.
+ * This API works only for device with {@link PackageManager#FEATURE_AUTOMOTIVE} feature
+ * supported or device with Google play service.
+ * Calling this API with device with no such feature will lead into an exception.
+ *
+ */
+public class Car {
+
+ /** Service name for getting car sensor service in {@link #getCarManager(String)}. */
+ public static final String SENSOR_SERVICE = "sensor";
+
+ /**
+ * Service for testing. This is system app only feature.
+ * @hide
+ */
+ public static final String TEST_SERVICE = "car-service-test";
+
+ /** Type of car connection: car emulator, not 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 WIFI. */
+ public static final int CONNECTION_TYPE_WIFI = 2;
+ /** Type of car connection: on-device car emulator, for development (e.g. Local Head Unit). */
+ public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3;
+ /** Type of car connection: car emulator, connected over ADB (e.g. 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;
+
+ /** @hide */
+ @IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI,
+ CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR, CONNECTION_TYPE_EMBEDDED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConnectionType {}
+
+ /** permission necessary to access car's mileage information */
+ public static final String PERMISSION_MILEAGE = "android.support.car.permission.CAR_MILEAGE";
+ /** permission necessary to access car's fuel level */
+ public static final String PERMISSION_FUEL = "android.support.car.permission.CAR_FUEL";
+ /** permission necessary to access car's speed */
+ public static final String PERMISSION_SPEED = "android.support.car.permission.CAR_SPEED";
+ /** permission necessary to access car specific communication channel */
+ public static final String PERMISSION_VENDOR_EXTENSION =
+ "android.support.car.permission.CAR_VENDOR_EXTENSION";
+
+ /** @hide */
+ public static final String CAR_SERVICE_INTERFACE_NAME = "android.support.car.ICar";
+
+ /**
+ * PackageManager.FEATURE_AUTOMOTIVE from M. But redefine here to support L.
+ * @hide
+ */
+ private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+ /**
+ * {@link CarServiceLoader} implementation for projected mode. Only available when projected
+ * client library is linked.
+ * @hide
+ */
+ private static final String PROJECTED_CAR_SERVICE_LOADER =
+ "com.google.android.gms.car.GoogleCarServiceLoader";
+
+ private static final int VERSION = 1;
+
+ private final Context mContext;
+ private final Looper mLooper;
+ @GuardedBy("this")
+ private ICar mService;
+ 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;
+ @GuardedBy("this")
+ private int mServiceVersion = 1; // default
+
+ private final ServiceConnectionListener mServiceConnectionListener =
+ new ServiceConnectionListener () {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mService = ICar.Stub.asInterface(service);
+ mConnectionState = STATE_CONNECTED;
+ // getVersion can fail but let it pass through as it is better to
+ // throw right exception in next car api call.
+ try {
+ mServiceVersion = mService.getVersion();
+ } catch (RemoteException e) {
+ Log.w(CarLibLog.TAG_CAR, "RemoteException in getVersion", e);
+ }
+ }
+ if (mServiceVersion < VERSION) {
+ Log.w(CarLibLog.TAG_CAR, "Old service version:" + mServiceVersion +
+ " for client lib:" + VERSION);
+ }
+ mServiceConnectionListenerClient.onServiceConnected(name, service);
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ mService = null;
+ if (mConnectionState == STATE_DISCONNECTED) {
+ return;
+ }
+ mConnectionState = STATE_CONNECTING;
+ }
+ mServiceConnectionListenerClient.onServiceDisconnected(name);
+ connect();
+ }
+
+ public void onServiceSuspended(int cause) {
+ mServiceConnectionListenerClient.onServiceSuspended(cause);
+ }
+
+ public void onServiceConnectionFailed(int cause) {
+ mServiceConnectionListenerClient.onServiceConnectionFailed(cause);
+ }
+ };
+
+ private final ServiceConnectionListener mServiceConnectionListenerClient;
+ private final Object mCarManagerLock = new Object();
+ @GuardedBy("mCarManagerLock")
+ private final HashMap<String, CarManagerBase> mServiceMap = new HashMap<>();
+ private final CarServiceLoader mCarServiceLoader;
+
+ /**
+ * Create Car instance for all Car API access.
+ * @param context
+ * @param serviceConnectionListener listner for monitoring service connection.
+ * @param looper Looper to dispatch all listeners. If null, it will use main thread. Note that
+ * service connection listener will be always in main thread regardless of this Looper.
+ */
+ public Car(Context context, ServiceConnectionListener serviceConnectionListener,
+ @Nullable Looper looper) {
+ mContext = context;
+ mServiceConnectionListenerClient = serviceConnectionListener;
+ if (looper == null) {
+ mLooper = Looper.getMainLooper();
+ } else {
+ mLooper = looper;
+ }
+ if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
+ mCarServiceLoader = new DefaultCarServiceLoader(context, mServiceConnectionListener);
+ } else {
+ Class carServiceLoaderClass = null;
+ try {
+ carServiceLoaderClass = Class.forName(PROJECTED_CAR_SERVICE_LOADER);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Cannot find CarServiceLoader implementation:" +
+ PROJECTED_CAR_SERVICE_LOADER, e);
+ }
+ Constructor<?> ctor;
+ try {
+ ctor = carServiceLoaderClass.getDeclaredConstructor(Context.class,
+ ServiceConnectionListener.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Cannot construct CarServiceLoader, no constructor: " +
+ PROJECTED_CAR_SERVICE_LOADER, e);
+ }
+ try {
+ mCarServiceLoader = (CarServiceLoader) ctor.newInstance(context,
+ serviceConnectionListener);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ throw new RuntimeException(
+ "Cannot construct CarServiceLoader, constructor failed for "
+ + carServiceLoaderClass.getName(), e);
+ }
+ }
+ }
+
+ /**
+ * Car constructor when ICar binder is already available.
+ * @param context
+ * @param service
+ * @param looper
+ *
+ * @hide
+ */
+ public Car(Context context, ICar service, @Nullable Looper looper) {
+ mContext = context;
+ if (looper == null) {
+ mLooper = Looper.getMainLooper();
+ } else {
+ mLooper = looper;
+ }
+ mService = service;
+ mConnectionState = STATE_CONNECTED;
+ mCarServiceLoader = null;
+ mServiceConnectionListenerClient = null;
+ }
+
+ /**
+ * Connect to car service. This can be called while it is disconnected.
+ * @throws IllegalStateException If connection is still on-going from previous
+ * connect call or it is already connected
+ */
+ 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. This can be called while disconnected. Once disconnect is
+ * called, all Car*Managers from this instance becomes invalid, and
+ * {@link Car#getCarManager(String)} will return different instance if it is connected again.
+ */
+ public void disconnect() {
+ synchronized (this) {
+ if (mConnectionState == STATE_DISCONNECTED) {
+ return;
+ }
+ tearDownCarManagers();
+ mService = null;
+ mConnectionState = STATE_DISCONNECTED;
+ mCarServiceLoader.disconnect();
+ }
+ }
+
+ /**
+ * Tells if it is connected to the service or not. This will return false if it is still
+ * connecting.
+ * @return
+ */
+ public boolean isConnected() {
+ synchronized (this) {
+ return mService != null;
+ }
+ }
+
+ /**
+ * Tells if this instance is already connecting to car service or not.
+ * @return
+ */
+ public boolean isConnecting() {
+ synchronized (this) {
+ return mConnectionState == STATE_CONNECTING;
+ }
+ }
+
+ /**
+ * Get car specific service as in {@link Context#getSystemService(String)}. Returned
+ * {@link Object} should be type-casted to the desired service.
+ * For example, to get sensor service,
+ * SensorManagerService sensorManagerService = car.getCarManager(Car.SENSOR_SERVICE);
+ * @param serviceName Name of service that should be created like {@link #SENSOR_SERVICE}.
+ * @return Matching service manager or null if there is no such service.
+ * @throws CarNotConnectedException
+ */
+ public Object getCarManager(String serviceName) throws CarNotConnectedException {
+ CarManagerBase manager = null;
+ synchronized (mCarManagerLock) {
+ //TODO filter known service vs unknown
+ manager = mServiceMap.get(serviceName);
+ if (manager == null) {
+ try {
+ IBinder binder = mService.getCarService(serviceName);
+ if (binder == null) {
+ Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" +
+ serviceName);
+ return null;
+ }
+ manager = createCarManager(serviceName, binder);
+ if (manager == null) {
+ Log.w(CarLibLog.TAG_CAR,
+ "getCarManager could not create manager for service:" +
+ serviceName);
+ return null;
+ }
+ mServiceMap.put(serviceName, manager);
+ } catch (RemoteException e) {
+ handleRemoteExceptionAndThrow(e);
+ }
+ }
+ }
+ return manager;
+ }
+
+ /**
+ * Return the type of currently connected car.
+ * @return
+ * @throws CarNotConnectedException
+ */
+ @ConnectionType
+ public int getCarConnectionType() throws CarNotConnectedException {
+ ICar service = getICarOrThrow();
+ try {
+ return service.getCarConnectionType();
+ } catch (RemoteException e) {
+ handleRemoteExceptionAndThrow(e);
+ }
+ return Car.CONNECTION_TYPE_EMULATOR;
+ }
+
+ private CarManagerBase createCarManager(String serviceName, IBinder binder) {
+ CarManagerBase manager = null;
+ switch (serviceName) {
+ case SENSOR_SERVICE:
+ manager = new CarSensorManager(mContext, ICarSensor.Stub.asInterface(binder),
+ mLooper);
+ break;
+ default:
+ manager = mCarServiceLoader.createCustomCarManager(serviceName, binder);
+ break;
+ }
+ return manager;
+ }
+
+ private synchronized ICar getICarOrThrow() throws IllegalStateException {
+ if (mService == null) {
+ throw new IllegalStateException("not connected");
+ }
+ return mService;
+ }
+
+ private void handleRemoteException(RemoteException e) {
+ Log.w(CarLibLog.TAG_CAR, "RemoteException", e);
+ disconnect();
+ }
+
+ private void handleRemoteExceptionAndThrow(RemoteException e) throws CarNotConnectedException {
+ handleRemoteException(e);
+ throw new CarNotConnectedException(e);
+ }
+
+ private void tearDownCarManagers() {
+ synchronized (mCarManagerLock) {
+ for (CarManagerBase manager: mServiceMap.values()) {
+ manager.onCarDisconnected();
+ }
+ mServiceMap.clear();
+ }
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarApiUtil.java b/carsupport-lib/src/android/support/car/CarApiUtil.java
new file mode 100644
index 0000000000..f3d3dbae26
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarApiUtil.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+/**
+ * Internal helper utilities
+ * @hide
+ */
+public class CarApiUtil {
+
+ /**
+ * CarService throws IllegalStateException with this message is re-thrown as
+ * {@link CarNotConnectedException}.
+ *
+ * @hide
+ */
+ public static final String CAR_NOT_CONNECTED_EXCEPTION_MSG = "CarNotConnected";
+
+ /**
+ * CarService throw IllegalStateException with this message is re-thrown as
+ * {@link CarNotSupportedException}.
+ *
+ * @hide
+ */
+ public static final String CAR_NOT_SUPPORTED_EXCEPTION_MSG = "CarNotSupported";
+
+ /**
+ * IllegalStateException from CarService with special message is re-thrown as a different
+ * exception.
+ *
+ * @param e exception from CarService
+ * @throws CarNotConnectedException
+ * @throws CarNotSupportedException
+ * @hide
+ */
+ public static void checkAllIllegalStateExceptionsFromCarService(IllegalStateException e)
+ throws CarNotConnectedException, CarNotSupportedException {
+ String message = e.getMessage();
+ if (message.equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) {
+ throw new CarNotConnectedException();
+ } else if (message.equals(CAR_NOT_SUPPORTED_EXCEPTION_MSG)) {
+ throw new CarNotSupportedException();
+ } else {
+ throw e;
+ }
+ }
+
+ /**
+ * Re-throw IllegalStateException from CarService with
+ * {@link #CAR_NOT_CONNECTED_EXCEPTION_MSG} message as {@link CarNotConnectedException}.
+ * exception.
+ *
+ * @param e exception from CarService
+ * @throws CarNotConnectedException
+ * @hide
+ */
+ public static void checkCarNotConnectedExceptionFromCarService(IllegalStateException e)
+ throws CarNotConnectedException {
+ if (e.getMessage().equals(CAR_NOT_CONNECTED_EXCEPTION_MSG)) {
+ throw new CarNotConnectedException();
+ } else {
+ throw e;
+ }
+ }
+
+}
diff --git a/carsupport-lib/src/android/support/car/CarInfo.aidl b/carsupport-lib/src/android/support/car/CarInfo.aidl
new file mode 100644
index 0000000000..0978cc4bf1
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable CarInfo;
diff --git a/carsupport-lib/src/android/support/car/CarInfo.java b/carsupport-lib/src/android/support/car/CarInfo.java
new file mode 100644
index 0000000000..756479758c
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarInfo.java
@@ -0,0 +1,50 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+//TODO
+public class CarInfo implements Parcelable {
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // TODO Auto-generated method stub
+ }
+
+ public static final Parcelable.Creator<CarInfo> CREATOR
+ = new Parcelable.Creator<CarInfo>() {
+ public CarInfo createFromParcel(Parcel in) {
+ return new CarInfo(in);
+ }
+
+ public CarInfo[] newArray(int size) {
+ return new CarInfo[size];
+ }
+ };
+
+ private CarInfo(Parcel in) {
+ //TODO
+ }
+
+}
diff --git a/carsupport-lib/src/android/support/car/CarLibLog.java b/carsupport-lib/src/android/support/car/CarLibLog.java
new file mode 100644
index 0000000000..926f6f06af
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarLibLog.java
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+public class CarLibLog {
+ public static final String TAG_CAR = "CAR.L";
+ public static final String TAG_SENSOR = "CAR.L.SENSOR";
+}
diff --git a/carsupport-lib/src/android/support/car/CarManagerBase.java b/carsupport-lib/src/android/support/car/CarManagerBase.java
new file mode 100644
index 0000000000..832188c078
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarManagerBase.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * Common interface for Car*Manager
+ */
+interface CarManagerBase {
+ void onCarDisconnected();
+}
diff --git a/carsupport-lib/src/android/support/car/CarNotConnectedException.java b/carsupport-lib/src/android/support/car/CarNotConnectedException.java
new file mode 100644
index 0000000000..aa3f1c1b65
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarNotConnectedException.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+
+/**
+ * Exception thrown when car is not connected for the API which requires car connection.
+ */
+public class CarNotConnectedException extends Exception {
+ private static final long serialVersionUID = -5629175439268813047L;
+
+ public CarNotConnectedException() {
+ }
+
+ public CarNotConnectedException(String name) {
+ super(name);
+ }
+
+ public CarNotConnectedException(String name, Throwable cause) {
+ super(name, cause);
+ }
+
+ public CarNotConnectedException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarNotSupportedException.java b/carsupport-lib/src/android/support/car/CarNotSupportedException.java
new file mode 100644
index 0000000000..a9522166d1
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarNotSupportedException.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+
+/**
+ * Exception thrown when car is not supporting the requested operation.
+ */
+public class CarNotSupportedException extends Exception {
+ private static final long serialVersionUID = -8120487541467522808L;
+
+ public CarNotSupportedException() {
+ }
+
+ public CarNotSupportedException(String name) {
+ super(name);
+ }
+
+ public CarNotSupportedException(String name, Throwable cause) {
+ super(name, cause);
+ }
+
+ public CarNotSupportedException(Exception cause) {
+ super(cause);
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarSensorEvent.aidl b/carsupport-lib/src/android/support/car/CarSensorEvent.aidl
new file mode 100644
index 0000000000..5aea5d3bf6
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarSensorEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable CarSensorEvent;
diff --git a/carsupport-lib/src/android/support/car/CarSensorEvent.java b/carsupport-lib/src/android/support/car/CarSensorEvent.java
new file mode 100644
index 0000000000..fa5a187afd
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarSensorEvent.java
@@ -0,0 +1,806 @@
+/*
+ * 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.location.GpsSatellite;
+import android.location.Location;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A CarSensorEvent object corresponds to a single sensor event coming from the car. The sensor
+ * data is stored in a sensor-type specific format in the object's float and byte arrays.
+ *
+ * To aid unmarshalling the object's data arrays, this class provides static nested classes and
+ * conversion methods, for example {@link EnvironmentData} and {@link #getEnvironmentData}. The
+ * conversion methods each have an optional data parameter which, if not null, will be used and
+ * returned. This parameter should be used to avoid unnecessary object churn whenever possible.
+ * Additionally, calling a conversion method on a CarSensorEvent object with an inappropriate type
+ * will result in an {@code UnsupportedOperationException} being thrown.
+ */
+public class CarSensorEvent implements Parcelable {
+
+ private static final int VERSION = 1;
+
+ final int mVersionCode;
+
+ /**
+ * Index in {@link #floatValues} for {@link CarSensorManager#SENSOR_TYPE_FUEL_LEVEL} type of
+ * sensor. This value is fuel level in percentile.
+ */
+ public static final int INDEX_FUEL_LEVEL_IN_PERCENTILE = 0;
+ /**
+ * Index in {@link #floatValues} for {@link CarSensorManager#SENSOR_TYPE_FUEL_LEVEL} type of
+ * sensor. This value is fuel level in coverable distance.
+ */
+ public static final int INDEX_FUEL_LEVEL_IN_DISTANCE = 1;
+ /**
+ * Index in {@link #byteValues} for {@link CarSensorManager#SENSOR_TYPE_FUEL_LEVEL} type of
+ * sensor. This value is set to 0 if fuel low level warning is on.
+ */
+ public static final int INDEX_FUEL_LOW_WARNING = 0;
+
+ /**
+ * GEAR_* represents meaning of byteValues[0] for {@link CarSensorManager#SENSOR_TYPE_GEAR}
+ * sensor type.
+ * GEAR_NEUTRAL means transmission gear is in neutral state, and the car may be moving.
+ */
+ public static final int GEAR_NEUTRAL = 0;
+ /**
+ * intValues[0] from 1 to 99 represents transmission gear number for moving forward.
+ * GEAR_FIRST is for gear number 1.
+ */
+ public static final int GEAR_FIRST = 1;
+ /** Gear number 2. */
+ public static final int GEAR_SECOND = 2;
+ /** Gear number 3. */
+ public static final int GEAR_THIRD = 3;
+ /** Gear number 4. */
+ public static final int GEAR_FOURTH = 4;
+ /** Gear number 5. */
+ public static final int GEAR_FIFTH = 5;
+ /** Gear number 6. */
+ public static final int GEAR_SIXTH = 6;
+ /** Gear number 7. */
+ public static final int GEAR_SEVENTH = 7;
+ /** Gear number 8. */
+ public static final int GEAR_EIGHTH = 8;
+ /** Gear number 9. */
+ public static final int GEAR_NINTH = 9;
+ /** Gear number 10. */
+ public static final int GEAR_TENTH = 10;
+ /**
+ * This is for transmission without specific gear number for moving forward like CVT. It tells
+ * that car is in a transmission state to move it forward.
+ */
+ public static final int GEAR_DRIVE = 100;
+ /** Gear in parking state */
+ public static final int GEAR_PARK = 101;
+ /** Gear in reverse */
+ public static final int GEAR_REVERSE = 102;
+
+ /**
+ * Bitmask of driving restrictions.
+ */
+ /** No restrictions. */
+ public static final int DRIVE_STATUS_UNRESTRICTED = 0;
+ /** No video playback allowed. */
+ public static final int DRIVE_STATUS_NO_VIDEO = 0x1;
+ /** No keyboard or rotary controller input allowed. */
+ public static final int DRIVE_STATUS_NO_KEYBOARD_INPUT = 0x2;
+ /** No voice input allowed. */
+ public static final int DRIVE_STATUS_NO_VOICE_INPUT = 0x4;
+ /** No setup / configuration allowed. */
+ public static final int DRIVE_STATUS_NO_CONFIG = 0x8;
+ /** Limit displayed message length. */
+ public static final int DRIVE_STATUS_LIMIT_MESSAGE_LEN = 0x10;
+ /** represents case where all of the above items are restricted */
+ public static final int DRIVE_STATUS_FULLY_RESTRICTED = DRIVE_STATUS_NO_VIDEO |
+ DRIVE_STATUS_NO_KEYBOARD_INPUT | DRIVE_STATUS_NO_VOICE_INPUT | DRIVE_STATUS_NO_CONFIG |
+ DRIVE_STATUS_LIMIT_MESSAGE_LEN;
+ /**
+ * Index for {@link CarSensorManager#SENSOR_TYPE_LOCATION} in floatValues.
+ * Each bit byteValues[0] represents whether the corresponding data is present.
+ */
+ public static final int INDEX_LOCATION_LATITUDE = 0;
+ public static final int INDEX_LOCATION_LONGITUDE = 1;
+ public static final int INDEX_LOCATION_ACCURACY = 2;
+ public static final int INDEX_LOCATION_ALTITUDE = 3;
+ public static final int INDEX_LOCATION_SPEED = 4;
+ public static final int INDEX_LOCATION_BEARING = 5;
+ public static final int INDEX_LOCATION_MAX = INDEX_LOCATION_BEARING;
+ public static final int INDEX_LOCATION_LATITUDE_BYTES = 1;
+ public static final int INDEX_LOCATION_LONGITUDE_BYTES = 5;
+
+ /**
+ * Index for {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT} in floatValues.
+ * Temperature in Celsius degrees.
+ */
+ public static final int INDEX_ENVIRONMENT_TEMPERATURE = 0;
+ /**
+ * Index for {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT} in floatValues.
+ * Pressure in kPa.
+ */
+ public static final int INDEX_ENVIRONMENT_PRESSURE = 1;
+
+ /**
+ * Index for {@link CarSensorManager#SENSOR_TYPE_HVAC} in floatValues.
+ * Target temperature set in Celsius degrees.
+ */
+ public static final int INDEX_HVAC_TARGET_TEMPERATURE = 0;
+ /**
+ * Index for {@link CarSensorManager#SENSOR_TYPE_HVAC} in floatValues.
+ * The current temperature set in Celsius degrees.
+ */
+ public static final int INDEX_HVAC_CURRENT_TEMPERATURE = 1;
+
+ /**
+ * Indices for {@link CarSensorManager#SENSOR_TYPE_COMPASS} in floatValues.
+ * Angles are in degrees. Pitch or/and roll can be NaN if it is not available.
+ */
+ public static final int INDEX_COMPASS_BEARING = 0;
+ public static final int INDEX_COMPASS_PITCH = 1;
+ public static final int INDEX_COMPASS_ROLL = 2;
+
+ /**
+ * Indices for {@link CarSensorManager#SENSOR_TYPE_ACCELEROMETER} in floatValues.
+ * Acceleration (gravity) is in m/s^2. Any component can be NaN if it is not available.
+ */
+ public static final int INDEX_ACCELEROMETER_X = 0;
+ public static final int INDEX_ACCELEROMETER_Y = 1;
+ public static final int INDEX_ACCELEROMETER_Z = 2;
+
+ /**
+ * Indices for {@link CarSensorManager#SENSOR_TYPE_GYROSCOPE} in floatValues.
+ * Rotation speed is in rad/s. Any component can be NaN if it is not available.
+ */
+ public static final int INDEX_GYROSCOPE_X = 0;
+ public static final int INDEX_GYROSCOPE_Y = 1;
+ public static final int INDEX_GYROSCOPE_Z = 2;
+
+ /**
+ * Indices for {@link CarSensorManager#SENSOR_TYPE_GPS_SATELLITE}.
+ * Both byte values and float values are used.
+ * Two first bytes encode number of satellites in-use/in-view (or 0xFF if unavailable).
+ * Then optionally with INDEX_GPS_SATELLITE_ARRAY_BYTE_OFFSET offset and interval
+ * INDEX_GPS_SATELLITE_ARRAY_BYTE_INTERVAL between elements are encoded boolean flags of whether
+ * particular satellite from in-view participate in in-use subset.
+ * Float values with INDEX_GPS_SATELLITE_ARRAY_FLOAT_OFFSET offset and interval
+ * INDEX_GPS_SATELLITE_ARRAY_FLOAT_INTERVAL between elements can optionally contain
+ * per-satellite values of signal strength and other values or NaN if unavailable.
+ */
+ public static final int INDEX_GPS_SATELLITE_NUMBER_IN_USE = 0;
+ public static final int INDEX_GPS_SATELLITE_NUMBER_IN_VIEW = 1;
+ public static final int INDEX_GPS_SATELLITE_ARRAY_BYTE_OFFSET = 2;
+ public static final int INDEX_GPS_SATELLITE_ARRAY_BYTE_INTERVAL = 1;
+ public static final int INDEX_GPS_SATELLITE_ARRAY_FLOAT_OFFSET = 0;
+ public static final int INDEX_GPS_SATELLITE_ARRAY_FLOAT_INTERVAL = 4;
+ public static final int INDEX_GPS_SATELLITE_PRN_OFFSET = 0;
+ public static final int INDEX_GPS_SATELLITE_SNR_OFFSET = 1;
+ public static final int INDEX_GPS_SATELLITE_AZIMUTH_OFFSET = 2;
+ public static final int INDEX_GPS_SATELLITE_ELEVATION_OFFSET = 3;
+
+ private static final long MILLI_IN_NANOS = 1000000L;
+
+ /** Sensor type for this event like {@link CarSensorManager#SENSOR_TYPE_CAR_SPEED}. */
+ public int sensorType;
+
+ /**
+ * When this data was acquired in car or received from car. It is elapsed real-time of data
+ * reception from car in nanoseconds since system boot.
+ */
+ public long timeStampNs;
+ /**
+ * array holding float type of sensor data. If the sensor has single value, only floatValues[0]
+ * should be used. */
+ public final float[] floatValues;
+ /** array holding byte type of sensor data */
+ public final byte[] byteValues;
+
+ public CarSensorEvent(
+ int versionCode,
+ int sensorType,
+ long timeStampNs,
+ float[] floatValues,
+ byte[] byteValues) {
+ this.mVersionCode = versionCode;
+ this.sensorType = sensorType;
+ this.timeStampNs = timeStampNs;
+ this.floatValues = floatValues;
+ this.byteValues = byteValues;
+ }
+
+ public CarSensorEvent(Parcel in) {
+ mVersionCode = in.readInt();
+ sensorType = in.readInt();
+ timeStampNs = in.readLong();
+ int len = in.readInt();
+ floatValues = new float[len];
+ in.readFloatArray(floatValues);
+ len = in.readInt();
+ byteValues = new byte[len];
+ in.readByteArray(byteValues);
+ // version 1 up to here
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mVersionCode);
+ dest.writeInt(sensorType);
+ dest.writeLong(timeStampNs);
+ dest.writeInt(floatValues.length);
+ dest.writeFloatArray(floatValues);
+ dest.writeInt(byteValues.length);
+ dest.writeByteArray(byteValues);
+ // version 1 up to here
+ }
+
+ public static final Parcelable.Creator<CarSensorEvent> CREATOR
+ = new Parcelable.Creator<CarSensorEvent>() {
+ public CarSensorEvent createFromParcel(Parcel in) {
+ return new CarSensorEvent(in);
+ }
+
+ public CarSensorEvent[] newArray(int size) {
+ return new CarSensorEvent[size];
+ }
+ };
+
+ /**
+ * @return version code of this Parcelable.
+ */
+ public int getVersionCode() {
+ return mVersionCode;
+ }
+
+ public CarSensorEvent(int sensorType, long timeStampNs, int floatValueSize, int byteValueSize) {
+ mVersionCode = VERSION;
+ this.sensorType = sensorType;
+ this.timeStampNs = timeStampNs;
+ floatValues = new float[floatValueSize];
+ byteValues = new byte[byteValueSize];
+ }
+
+ /** @hide */
+ CarSensorEvent(int sensorType, long timeStampNs, float[] floatValues, byte[] byteValues) {
+ mVersionCode = VERSION;
+ this.sensorType = sensorType;
+ this.timeStampNs = timeStampNs;
+ this.floatValues = floatValues;
+ this.byteValues = byteValues;
+ }
+
+ private void checkType(int type) {
+ if (sensorType == type) {
+ return;
+ }
+ throw new UnsupportedOperationException(String.format(
+ "Invalid sensor type: expected %d, got %d", type, sensorType));
+ }
+
+ public static class EnvironmentData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is NaN. */
+ public float temperature;
+ /** If unsupported by the car, this value is NaN. */
+ public float pressure;
+ }
+
+ /**
+ * Convenience method for obtaining an {@link EnvironmentData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_ENVIRONMENT}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return an EnvironmentData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public EnvironmentData getEnvironmentData(EnvironmentData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_ENVIRONMENT);
+ if (data == null) {
+ data = new EnvironmentData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.temperature = floatValues[INDEX_ENVIRONMENT_TEMPERATURE];
+ data.pressure = floatValues[INDEX_ENVIRONMENT_PRESSURE];
+ return data;
+ }
+
+ public static class HvacData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is NaN. */
+ public float targetTemperature;
+ /** If unsupported by the car, this value is NaN. */
+ public float currentTemperature;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link HvacData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_HVAC}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a HvacData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public HvacData getHvacData(HvacData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_HVAC);
+ if (data == null) {
+ data = new HvacData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.targetTemperature = floatValues[INDEX_ENVIRONMENT_TEMPERATURE];
+ data.currentTemperature = floatValues[INDEX_ENVIRONMENT_PRESSURE];
+ return data;
+ }
+
+ public static class NightData {
+ public long timeStampNs;
+ public boolean isNightMode;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link NightData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_NIGHT}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a NightData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public NightData getNightData(NightData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_NIGHT);
+ if (data == null) {
+ data = new NightData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.isNightMode = byteValues[0] == 1;
+ return data;
+ }
+
+ public static class GearData {
+ public long timeStampNs;
+ public int gear;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link GearData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_GEAR}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a GearData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public GearData getGearData(GearData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_GEAR);
+ if (data == null) {
+ data = new GearData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.gear = byteValues[0];
+ return data;
+ }
+
+ public static class ParkingBrakeData {
+ public long timeStampNs;
+ public boolean isEngaged;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link ParkingBrakeData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_PARKING_BRAKE}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a ParkingBreakData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public ParkingBrakeData getParkingBrakeData(ParkingBrakeData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
+ if (data == null) {
+ data = new ParkingBrakeData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.isEngaged = byteValues[0] == 1;
+ return data;
+ }
+
+ public static class FuelLevelData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is 0. */
+ public int level;
+ /** If unsupported by the car, this value is 0. */
+ public int range;
+ /** If unsupported by the car, this value is false. */
+ public boolean lowFuelWarning;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link FuelLevelData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_FUEL_LEVEL}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a FuelLevel object corresponding to the data contained in the CarSensorEvent.
+ */
+ public FuelLevelData getFuelLevelData(FuelLevelData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_FUEL_LEVEL);
+ if (data == null) {
+ data = new FuelLevelData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.level = (int) floatValues[INDEX_FUEL_LEVEL_IN_PERCENTILE];
+ data.range = (int) floatValues[INDEX_FUEL_LEVEL_IN_DISTANCE];
+ data.lowFuelWarning = byteValues[0] == 1;
+ return data;
+ }
+
+ public static class OdometerData {
+ public long timeStampNs;
+ public float kms;
+ }
+
+ /**
+ * Convenience method for obtaining an {@link OdometerData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_ODOMETER}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return an OdometerData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public OdometerData getOdometerData(OdometerData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_ODOMETER);
+ if (data == null) {
+ data = new OdometerData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.kms = floatValues[0];
+ return data;
+ }
+
+ public static class RpmData {
+ public long timeStampNs;
+ public float rpm;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link RpmData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_RPM}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a RpmData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public RpmData getRpmData(RpmData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_RPM);
+ if (data == null) {
+ data = new RpmData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.rpm = floatValues[0];
+ return data;
+ }
+
+ public static class CarSpeedData {
+ public long timeStampNs;
+ public float carSpeed;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link CarSpeedData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_CAR_SPEED}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a CarSpeedData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public CarSpeedData getCarSpeedData(CarSpeedData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_CAR_SPEED);
+ if (data == null) {
+ data = new CarSpeedData();
+ }
+ data.timeStampNs = timeStampNs;
+ data.carSpeed = floatValues[0];
+ return data;
+ }
+
+ public static class CompassData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is NaN. */
+ public float bearing;
+ /** If unsupported by the car, this value is NaN. */
+ public float pitch;
+ /** If unsupported by the car, this value is NaN. */
+ public float roll;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link CompassData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_COMPASS}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a CompassData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public CompassData getCompassData(CompassData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_COMPASS);
+ if (data == null) {
+ data = new CompassData();
+ }
+ data.bearing = floatValues[INDEX_COMPASS_BEARING];
+ data.pitch = floatValues[INDEX_COMPASS_PITCH];
+ data.roll = floatValues[INDEX_COMPASS_ROLL];
+ return data;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link Location} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_LOCATION}.
+ *
+ * @param location an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a Location object corresponding to the data contained in the CarSensorEvent.
+ */
+ public Location getLocation(Location location) {
+ checkType(CarSensorManager.SENSOR_TYPE_LOCATION);
+ if (location == null) {
+ location = new Location("Car-GPS");
+ }
+ // byteValues[0]: bit flags for the presence of other values following.
+ int presense = byteValues[0];
+ if ((presense & (0x1 << INDEX_LOCATION_LATITUDE)) != 0) {
+ int latBytes = unpackBytes(byteValues, INDEX_LOCATION_LATITUDE_BYTES);
+ location.setLatitude(latBytes * 1e-7);
+ }
+ if ((presense & (0x1 << INDEX_LOCATION_LONGITUDE)) != 0) {
+ int longBytes = unpackBytes(byteValues, INDEX_LOCATION_LONGITUDE_BYTES);
+ location.setLongitude(longBytes * 1e-7);
+ }
+ if ((presense & (0x1 << INDEX_LOCATION_ACCURACY)) != 0) {
+ location.setAccuracy(floatValues[INDEX_LOCATION_ACCURACY]);
+ }
+ if ((presense & (0x1 << INDEX_LOCATION_ALTITUDE)) != 0) {
+ location.setAltitude(floatValues[INDEX_LOCATION_ALTITUDE]);
+ }
+ if ((presense & (0x1 << INDEX_LOCATION_SPEED)) != 0) {
+ location.setSpeed(floatValues[INDEX_LOCATION_SPEED]);
+ }
+ if ((presense & (0x1 << INDEX_LOCATION_BEARING)) != 0) {
+ location.setBearing(floatValues[INDEX_LOCATION_BEARING]);
+ }
+ location.setElapsedRealtimeNanos(timeStampNs);
+ // There is a risk of scheduler delaying 2nd elapsedRealtimeNs value.
+ // But will not try to fix it assuming that is acceptable as UTC time's accuracy is not
+ // guaranteed in Location data.
+ long currentTimeMs = System.currentTimeMillis();
+ long elapsedRealtimeNs = SystemClock.elapsedRealtimeNanos();
+ location.setTime(
+ currentTimeMs - (elapsedRealtimeNs - timeStampNs) / MILLI_IN_NANOS);
+ return location;
+ }
+
+ public static class DrivingStatusData {
+ public long timeStampNs;
+ public byte status;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link DrivingStatusData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_DRIVING_STATUS}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a DrivingStatusData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public DrivingStatusData getDrivingStatusData(DrivingStatusData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_DRIVING_STATUS);
+ if (data == null) {
+ data = new DrivingStatusData();
+ }
+ data.status = byteValues[0];
+ return data;
+ }
+
+ public static class AccelerometerData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is NaN. */
+ public float x;
+ /** If unsupported by the car, this value is NaN. */
+ public float y;
+ /** If unsupported by the car, this value is NaN. */
+ public float z;
+ }
+
+ /**
+ * Convenience method for obtaining an {@link AccelerometerData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_ACCELEROMETER}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a AccelerometerData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public AccelerometerData getAccelerometerData(AccelerometerData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_ACCELEROMETER);
+ if (data == null) {
+ data = new AccelerometerData();
+ }
+ data.x = floatValues[INDEX_ACCELEROMETER_X];
+ data.y = floatValues[INDEX_ACCELEROMETER_Y];
+ data.z = floatValues[INDEX_ACCELEROMETER_Z];
+ return data;
+ }
+
+ public static class GyroscopeData {
+ public long timeStampNs;
+ /** If unsupported by the car, this value is NaN. */
+ public float x;
+ /** If unsupported by the car, this value is NaN. */
+ public float y;
+ /** If unsupported by the car, this value is NaN. */
+ public float z;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link GyroscopeData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_GYROSCOPE}.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @return a GyroscopeData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public GyroscopeData getGyroscopeData(GyroscopeData data) {
+ checkType(CarSensorManager.SENSOR_TYPE_GYROSCOPE);
+ if (data == null) {
+ data = new GyroscopeData();
+ }
+ data.x = floatValues[INDEX_GYROSCOPE_X];
+ data.y = floatValues[INDEX_GYROSCOPE_Y];
+ data.z = floatValues[INDEX_GYROSCOPE_Z];
+ return data;
+ }
+
+ // android.location.GpsSatellite doesn't have a public constructor, so that can't be used.
+ /**
+ * Class that contains GPS satellite status. For more info on meaning of these fields refer
+ * to the documentation to the {@link GpsSatellite} class.
+ */
+ public static class GpsSatelliteData {
+ public long timeStampNs;
+ /**
+ * Number of satellites used in GPS fix or -1 of unavailable.
+ */
+ public int numberInUse = -1;
+ /**
+ * Number of satellites in view or -1 of unavailable.
+ */
+ public int numberInView = -1;
+ /**
+ * Per-satellite flag if this satellite was used for GPS fix.
+ * Can be null if per-satellite data is unavailable.
+ */
+ public boolean[] usedInFix = null;
+ /**
+ * Per-satellite pseudo-random id.
+ * Can be null if per-satellite data is unavailable.
+ */
+ public int[] prn = null;
+ /**
+ * Per-satellite signal to noise ratio.
+ * Can be null if per-satellite data is unavailable.
+ */
+ public float[] snr = null;
+ /**
+ * Per-satellite azimuth.
+ * Can be null if per-satellite data is unavailable.
+ */
+ public float[] azimuth = null;
+ /**
+ * Per-satellite elevation.
+ * Can be null if per-satellite data is unavailable.
+ */
+ public float[] elevation = null;
+ }
+
+ /**
+ * Convenience method for obtaining a {@link GpsSatelliteData} object from a CarSensorEvent
+ * object with type {@link CarSensorManager#SENSOR_TYPE_HVAC} with optional per-satellite info.
+ *
+ * @param data an optional output parameter which, if non-null, will be used by this method
+ * instead of a newly created object.
+ * @param withPerSatellite whether to include per-satellite data.
+ * @return a GpsSatelliteData object corresponding to the data contained in the CarSensorEvent.
+ */
+ public GpsSatelliteData getGpsSatelliteData(GpsSatelliteData data,
+ boolean withPerSatellite) {
+ checkType(CarSensorManager.SENSOR_TYPE_GPS_SATELLITE);
+ if (data == null) {
+ data = new GpsSatelliteData();
+ }
+ final int byteOffset = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_BYTE_OFFSET;
+ final int byteInterval = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_BYTE_INTERVAL;
+ final int floatOffset = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_OFFSET;
+ final int floatInterval = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_INTERVAL;
+ final int numberOfSats = (floatValues.length - floatOffset) / floatInterval;
+
+ data.numberInUse = byteValues[CarSensorEvent.INDEX_GPS_SATELLITE_NUMBER_IN_USE];
+ data.numberInView = byteValues[CarSensorEvent.INDEX_GPS_SATELLITE_NUMBER_IN_VIEW];
+ if (withPerSatellite && data.numberInView >= 0) {
+ data.usedInFix = new boolean[numberOfSats];
+ data.prn = new int[numberOfSats];
+ data.snr = new float[numberOfSats];
+ data.azimuth = new float[numberOfSats];
+ data.elevation = new float[numberOfSats];
+
+ for (int i = 0; i < numberOfSats; ++i) {
+ int iByte = byteOffset + byteInterval * i;
+ int iFloat = floatOffset + floatInterval * i;
+ data.usedInFix[i] = byteValues[iByte] != 0;
+ data.prn[i] = Math.round(
+ floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_PRN_OFFSET]);
+ data.snr[i] =
+ floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_SNR_OFFSET];
+ data.azimuth[i] = floatValues[iFloat
+ + CarSensorEvent.INDEX_GPS_SATELLITE_AZIMUTH_OFFSET];
+ data.elevation[i] = floatValues[iFloat
+ + CarSensorEvent.INDEX_GPS_SATELLITE_ELEVATION_OFFSET];
+ }
+ }
+ return data;
+ }
+
+ /** @hide */
+ public static int unpackBytes(byte[] arr, int offset) {
+ int b0 = arr[offset] & 0xff;
+ int b1 = (arr[offset + 1] << 8) & 0xff00;
+ int b2 = (arr[offset + 2] << 16) & 0xff0000;
+ int b3 = (arr[offset + 3] << 24) & 0xff000000;
+ return b0 | b1 | b2 | b3;
+ }
+
+ /** @hide */
+ public static void packBytes(byte[] arr, int offset, int value) {
+ arr[offset] = (byte) (value & 0xff);
+ arr[offset + 1] = (byte) ((value >> 8) & 0xff);
+ arr[offset + 2] = (byte) ((value >> 16) & 0xff);
+ arr[offset + 3] = (byte) ((value >> 24) & 0xff);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getName() + "[");
+ sb.append("type:" + Integer.toHexString(sensorType));
+ if (floatValues != null && floatValues.length > 0) {
+ sb.append(" float values:");
+ for (float v: floatValues) {
+ sb.append(" " + v);
+ }
+ }
+ if (byteValues != null && byteValues.length > 0) {
+ sb.append(" byte values:");
+ for (byte v: byteValues) {
+ sb.append(" " + v);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarSensorManager.java b/carsupport-lib/src/android/support/car/CarSensorManager.java
new file mode 100644
index 0000000000..e407d730d4
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarSensorManager.java
@@ -0,0 +1,482 @@
+/*
+ * 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.Manifest;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Handler.Callback;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * API for monitoring car sensor data.
+ */
+public class CarSensorManager implements CarManagerBase {
+ /**
+ * SENSOR_TYPE_* represents type of sensor supported from the connected car.
+ * This sensor represents the direction of the car as an angle in degree measured clockwise
+ * with 0 degree pointing to north.
+ * Sensor data in {@link CarSensorEvent} is a float (floatValues[0]).
+ */
+ public static final int SENSOR_TYPE_COMPASS = 1;
+ /**
+ * This sensor represents vehicle speed in m/s.
+ * Sensor data in {@link CarSensorEvent} is a float which will be >= 0.
+ * This requires {@link Car#PERMISSION_SPEED} permission.
+ */
+ public static final int SENSOR_TYPE_CAR_SPEED = 2;
+ /**
+ * Represents engine RPM of the car. Sensor data in {@link CarSensorEvent} is a float.
+ */
+ public static final int SENSOR_TYPE_RPM = 3;
+ /**
+ * Total travel distance of the car in Kilometer. Sensor data is a float.
+ * This requires {@link Car#PERMISSION_MILEAGE} permission.
+ */
+ public static final int SENSOR_TYPE_ODOMETER = 4;
+ /**
+ * Indicates fuel level of the car.
+ * In {@link CarSensorEvent}, floatValues[{@link CarSensorEvent#INDEX_FUEL_LEVEL_IN_PERCENTILE}]
+ * represents fuel level in percentile (0 to 100) while
+ * floatValues[{@link CarSensorEvent#INDEX_FUEL_LEVEL_IN_DISTANCE}] represents estimated range
+ * in Kilometer with the remaining fuel.
+ * Note that the gas mileage used for the estimation may not represent the current driving
+ * condition.
+ * This requires {@link Car#PERMISSION_FUEL} permission.
+ */
+ public static final int SENSOR_TYPE_FUEL_LEVEL = 5;
+ /**
+ * Represents the current status of parking brake. Sensor data in {@link CarSensorEvent} is an
+ * int (byteValues[0]). Value of 1 represents parking brake applied while 0 means the other way
+ * around. For this sensor, rate in {@link #registerListener(CarSensorEventListener, int, int)}
+ * will be ignored and all changes will be notified.
+ */
+ public static final int SENSOR_TYPE_PARKING_BRAKE = 6;
+ /**
+ * This represents the current position of transmission gear. Sensor data in
+ * {@link CarSensorEvent} is an int (byteValues[0]). For the meaning of the value, check
+ * {@link CarSensorEvent#GEAR_NEUTRAL} and other GEAR_*.
+ */
+ public static final int SENSOR_TYPE_GEAR = 7;
+ /**
+ * Diagnostics message / event coming from car. The data is coming as byte array in
+ * {@link CarSensorEvent}.
+ * TODO: clarify data format
+ */
+ public static final int SENSOR_TYPE_DIAGNOSTICS = 8;
+ /**
+ * Day/night sensor. Sensor data is an int (byteValues[0]).
+ */
+ public static final int SENSOR_TYPE_NIGHT = 9;
+ /**
+ * Sensor type for location. Sensor data passed in floatValues.
+ */
+ public static final int SENSOR_TYPE_LOCATION = 10;
+ /**
+ * Represents the current driving status of car. Different user interaction should be used
+ * depending on the current driving status. Driving status is an int (byteValues[0]).
+ */
+ public static final int SENSOR_TYPE_DRIVING_STATUS = 11;
+ /**
+ * Environment like temperature and pressure.
+ */
+ public static final int SENSOR_TYPE_ENVIRONMENT = 12;
+ /**
+ * Current status of HVAC system like target temperature and current temperature.
+ */
+ public static final int SENSOR_TYPE_HVAC = 13;
+ public static final int SENSOR_TYPE_ACCELEROMETER = 14;
+ public static final int SENSOR_TYPE_DEAD_RECKONING = 15;
+ public static final int SENSOR_TYPE_DOOR = 16;
+ public static final int SENSOR_TYPE_GPS_SATELLITE = 17;
+ public static final int SENSOR_TYPE_GYROSCOPE = 18;
+ public static final int SENSOR_TYPE_LIGHT = 19;
+ public static final int SENSOR_TYPE_PASSENGER = 20;
+ public static final int SENSOR_TYPE_TIRE_PRESSURE = 21;
+ /** Sensor type bigger than this is invalid. Always update this after adding a new sensor. */
+ private static final int SENSOR_TYPE_MAX = SENSOR_TYPE_TIRE_PRESSURE;
+
+ /** Read sensor in default normal rate set for each sensors. */
+ public static final int SENSOR_RATE_NORMAL = 3;
+ /** Read sensor at the maximum rate. Actual rate will be different depending on the sensor */
+ public static final int SENSOR_RATE_FASTEST = 0;
+
+ private static final int MSG_SENSOR_EVENT = 0;
+
+ private static final int VERSION = 1;
+ private final ICarSensor mService;
+ private final int mServiceVersion;
+
+ private CarSensorEventListenerToService mCarSensorEventListenerToService;
+
+ /**
+ * To keep record of locally active sensors. Key is sensor type. This is used as a basic lock
+ * for all client accesses.
+ */
+ private final HashMap<Integer, CarSensorListeners> mActiveSensorListeners =
+ new HashMap<Integer, CarSensorListeners>();
+
+ /** Handles call back into projected apps. */
+ private final Handler mHandler;
+ private final Callback mHandlerCallback = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SENSOR_EVENT:
+ synchronized(mActiveSensorListeners) {
+ CarSensorEvent event = (CarSensorEvent) msg.obj;
+ CarSensorListeners listeners = mActiveSensorListeners.get(event.sensorType);
+ if (listeners != null) {
+ listeners.onSensorChanged(event);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+ };
+
+
+ /** @hide */
+ CarSensorManager(Context context, ICarSensor service, Looper looper) {
+ mService = service;
+ mServiceVersion = getVersion();
+ if (mServiceVersion < VERSION) {
+ Log.w(CarLibLog.TAG_SENSOR, "Old service version:" + mServiceVersion +
+ " for client lib:" + VERSION);
+ }
+ mHandler = new Handler(looper, mHandlerCallback);
+ }
+
+ /** @hide */
+ @Override
+ public void onCarDisconnected() {
+ synchronized(mActiveSensorListeners) {
+ mActiveSensorListeners.clear();
+ mCarSensorEventListenerToService = null;
+ }
+ }
+
+ private int getVersion() {
+ try {
+ return mService.getVersion();
+ } catch (RemoteException e) {
+ Log.w(CarLibLog.TAG_SENSOR, "Exception in getVersion", e);
+ }
+ return 1;
+ }
+
+ /**
+ * Give the list of CarSensors available in the connected car.
+ * @return array of all sensor types supported.
+ * @throws CarNotConnectedException
+ */
+ public int[] getSupportedSensors() throws CarNotConnectedException {
+ try {
+ return mService.getSupportedSensors();
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ //ignore
+ }
+ return new int[0];
+ }
+
+ /**
+ * Tells if given sensor is supported or not.
+ * @param sensorType
+ * @return true if the sensor is supported.
+ * @throws CarNotConnectedException
+ */
+ public boolean isSensorSupported(int sensorType) throws CarNotConnectedException {
+ int[] sensors = getSupportedSensors();
+ for (int sensorSupported: sensors) {
+ if (sensorType == sensorSupported) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if given sensorList is including the sensorType.
+ * @param sensorList
+ * @param sensorType
+ * @return
+ */
+ public static boolean isSensorSupported(int[] sensorList, int sensorType) {
+ for (int sensorSupported: sensorList) {
+ if (sensorType == sensorSupported) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for car sensor data change.
+ * Callbacks are called in the Looper context.
+ */
+ public interface CarSensorEventListener {
+ /**
+ * Called when there is a new sensor data from car.
+ * @param event Incoming sensor event for the given sensor type.
+ */
+ void onSensorChanged(final CarSensorEvent event);
+ }
+
+ /**
+ * Register {@link CarSensorEventListener} to get repeated sensor updates. Multiple listeners
+ * can be registered for a single sensor or the same listener can be used for different sensors.
+ * If the same listener is registered again for the same sensor, it will be either ignored or
+ * updated depending on the rate.
+ * <p>
+ * Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} for
+ * {@link #SENSOR_TYPE_LOCATION}, {@link Car#PERMISSION_SPEED} for
+ * {@link #SENSOR_TYPE_CAR_SPEED}, {@link Car#PERMISSION_MILEAGE} for
+ * {@link #SENSOR_TYPE_ODOMETER}, or {@link Car#PERMISSION_FUEL} for
+ * {@link #SENSOR_TYPE_FUEL_LEVEL}.
+ *
+ * @param listener
+ * @param sensorType sensor type to subscribe.
+ * @param rate how fast the sensor events are delivered. It should be one of
+ * {@link #SENSOR_RATE_FASTEST} or {@link #SENSOR_RATE_NORMAL}. Rate may not be respected
+ * especially when the same sensor is registered with different listener with different
+ * rates.
+ * @return if the sensor was successfully enabled.
+ * @throws CarNotConnectedException
+ * @throws IllegalArgumentException for wrong argument like wrong rate
+ * @throws SecurityException if missing the appropriate permission
+ */
+ @RequiresPermission(anyOf={Manifest.permission.ACCESS_FINE_LOCATION, Car.PERMISSION_SPEED,
+ Car.PERMISSION_MILEAGE, Car.PERMISSION_FUEL}, conditional=true)
+ public boolean registerListener(CarSensorEventListener listener, int sensorType, int rate)
+ throws CarNotConnectedException, IllegalArgumentException {
+ assertSensorType(sensorType);
+ if (rate != SENSOR_RATE_FASTEST && rate != SENSOR_RATE_NORMAL) {
+ throw new IllegalArgumentException("wrong rate " + rate);
+ }
+ synchronized(mActiveSensorListeners) {
+ if (mCarSensorEventListenerToService == null) {
+ mCarSensorEventListenerToService = new CarSensorEventListenerToService(this);
+ }
+ boolean needsServerUpdate = false;
+ CarSensorListeners listeners;
+ listeners = mActiveSensorListeners.get(sensorType);
+ if (listeners == null) {
+ listeners = new CarSensorListeners(rate);
+ mActiveSensorListeners.put(sensorType, listeners);
+ needsServerUpdate = true;
+ }
+ if (listeners.addAndUpdateRate(listener, rate)) {
+ needsServerUpdate = true;
+ }
+ if (needsServerUpdate) {
+ if (!registerOrUpdateSensorListener(sensorType, rate)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Stop getting sensor update for the given listener. If there are multiple registrations for
+ * this listener, all listening will be stopped.
+ * @param listener
+ */
+ public void unregisterListener(CarSensorEventListener listener) {
+ //TODO: removing listener should reset update rate
+ synchronized(mActiveSensorListeners) {
+ Iterator<Integer> sensorIterator = mActiveSensorListeners.keySet().iterator();
+ while (sensorIterator.hasNext()) {
+ Integer sensor = sensorIterator.next();
+ doUnregisterListenerLocked(listener, sensor, sensorIterator);
+ }
+ }
+ }
+
+ /**
+ * Stop getting sensor update for the given listener and sensor. If the same listener is used
+ * for other sensors, those subscriptions will not be affected.
+ * @param listener
+ * @param sensorType
+ */
+ public void unregisterListener(CarSensorEventListener listener, int sensorType) {
+ synchronized(mActiveSensorListeners) {
+ doUnregisterListenerLocked(listener, sensorType, null);
+ }
+ }
+
+ private void doUnregisterListenerLocked(CarSensorEventListener listener, Integer sensor,
+ Iterator<Integer> sensorIterator) {
+ CarSensorListeners listeners = mActiveSensorListeners.get(sensor);
+ if (listeners != null) {
+ if (listeners.contains(listener)) {
+ listeners.remove(listener);
+ }
+ if (listeners.isEmpty()) {
+ try {
+ mService.unregisterSensorListener(sensor.intValue(),
+ mCarSensorEventListenerToService);
+ } catch (RemoteException e) {
+ // ignore
+ }
+ if (sensorIterator == null) {
+ mActiveSensorListeners.remove(sensor);
+ } else {
+ sensorIterator.remove();
+ }
+ }
+ }
+ }
+
+ private boolean registerOrUpdateSensorListener(int sensor, int rate)
+ throws CarNotConnectedException {
+ try {
+ if (!mService.registerOrUpdateSensorListener(sensor, rate,
+ VERSION, mCarSensorEventListenerToService)) {
+ return false;
+ }
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the most recent CarSensorEvent for the given type.
+ * @param type A sensor to request
+ * @return null if there was no sensor update since connected to the car.
+ * @throws CarNotConnectedException
+ */
+ public CarSensorEvent getLatestSensorEvent(int type) throws CarNotConnectedException {
+ assertSensorType(type);
+ try {
+ return mService.getLatestSensorEvent(type);
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch(RemoteException e) {
+ handleCarServiceRemoteExceptionAndThrow(e);
+ }
+ return null;
+ }
+
+ private void handleCarServiceRemoteExceptionAndThrow(RemoteException e)
+ throws CarNotConnectedException {
+ if (Log.isLoggable(CarLibLog.TAG_SENSOR, Log.INFO)) {
+ Log.i(CarLibLog.TAG_SENSOR, "RemoteException from car service:" + e.getMessage());
+ }
+ throw new CarNotConnectedException();
+ }
+
+ private void assertSensorType(int sensorType) {
+ if (sensorType == 0 || sensorType > SENSOR_TYPE_MAX) {
+ throw new IllegalArgumentException("invalid sensor type " + sensorType);
+ }
+ }
+
+ private void handleOnSensorChanged(CarSensorEvent event) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SENSOR_EVENT, event));
+ }
+
+ private static class CarSensorEventListenerToService extends ICarSensorEventListener.Stub {
+ private final WeakReference<CarSensorManager> mManager;
+
+ public CarSensorEventListenerToService(CarSensorManager manager) {
+ mManager = new WeakReference<CarSensorManager>(manager);
+ }
+
+ @Override
+ public void onSensorChanged(CarSensorEvent event) {
+ CarSensorManager manager = mManager.get();
+ if (manager != null) {
+ manager.handleOnSensorChanged(event);
+ }
+ }
+ }
+
+ /**
+ * Represent listeners for a sensor.
+ */
+ private class CarSensorListeners {
+ private final LinkedList<CarSensorEventListener> mListeners =
+ new LinkedList<CarSensorEventListener>();
+
+ private int mUpdateRate;
+ private long mLastUpdateTime = -1;
+
+ CarSensorListeners(int rate) {
+ mUpdateRate = rate;
+ }
+
+ boolean contains(CarSensorEventListener listener) {
+ return mListeners.contains(listener);
+ }
+
+ void remove(CarSensorEventListener listener) {
+ mListeners.remove(listener);
+ }
+
+ boolean isEmpty() {
+ return mListeners.isEmpty();
+ }
+
+ /**
+ * Add given listener to the list and update rate if necessary.
+ * @param listener if null, add part is skipped.
+ * @param updateRate
+ * @return true if rate was updated. Otherwise, returns false.
+ */
+ boolean addAndUpdateRate(CarSensorEventListener listener, int updateRate) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ if (mUpdateRate > updateRate) {
+ mUpdateRate = updateRate;
+ return true;
+ }
+ return false;
+ }
+
+ void onSensorChanged(CarSensorEvent event) {
+ // throw away old sensor data as oneway binder call can change order.
+ long updateTime = event.timeStampNs;
+ if (updateTime < mLastUpdateTime) {
+ Log.w(CarLibLog.TAG_SENSOR, "dropping old sensor data");
+ return;
+ }
+ mLastUpdateTime = updateTime;
+ for (CarSensorEventListener listener: mListeners) {
+ listener.onSensorChanged(event);
+ }
+ }
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarServiceLoader.java b/carsupport-lib/src/android/support/car/CarServiceLoader.java
new file mode 100644
index 0000000000..198909355b
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarServiceLoader.java
@@ -0,0 +1,59 @@
+/*
+ * 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.IBinder;
+
+/**
+ * CarServiceLoader is the abstraction for loading different types of car service.
+ * @hide
+ */
+public abstract class CarServiceLoader {
+
+ private final Context mContext;
+ private final ServiceConnectionListener mListener;
+
+ public CarServiceLoader(Context context, ServiceConnectionListener listener) {
+ mContext = context;
+ mListener = listener;
+ }
+
+ public abstract void connect() throws IllegalStateException;
+ public abstract void disconnect();
+
+ /**
+ * Factory method to create non-standard Car*Manager for the given car service implementation.
+ * This is necessary for Car*Manager which is relevant for one specific implementation like
+ * projected.
+ * @param serviceName service name for the given Car*Manager.
+ * @param binder binder implementation received from car service
+ * @return Car*Manager instance for the given serviceName / binder. null if given service is
+ * not supported.
+ */
+ public CarManagerBase createCustomCarManager(String serviceName, IBinder binder) {
+ return null;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected ServiceConnectionListener getConnectionListener() {
+ return mListener;
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/CarUiInfo.aidl b/carsupport-lib/src/android/support/car/CarUiInfo.aidl
new file mode 100644
index 0000000000..43a4426cba
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarUiInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable CarUiInfo;
diff --git a/carsupport-lib/src/android/support/car/CarUiInfo.java b/carsupport-lib/src/android/support/car/CarUiInfo.java
new file mode 100644
index 0000000000..9df852de4f
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/CarUiInfo.java
@@ -0,0 +1,50 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+//TODO
+public class CarUiInfo implements Parcelable {
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ // TODO Auto-generated method stub
+ }
+
+ public static final Parcelable.Creator<CarUiInfo> CREATOR
+ = new Parcelable.Creator<CarUiInfo>() {
+ public CarUiInfo createFromParcel(Parcel in) {
+ return new CarUiInfo(in);
+ }
+
+ public CarUiInfo[] newArray(int size) {
+ return new CarUiInfo[size];
+ }
+ };
+
+ private CarUiInfo(Parcel in) {
+ //TODO
+ }
+
+}
diff --git a/carsupport-lib/src/android/support/car/DefaultCarServiceLoader.java b/carsupport-lib/src/android/support/car/DefaultCarServiceLoader.java
new file mode 100644
index 0000000000..f3fcc688a7
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/DefaultCarServiceLoader.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+/**
+ * Default CarServiceLoader for system with built-in car service.
+ * @hide
+ */
+public class DefaultCarServiceLoader extends CarServiceLoader {
+
+ private static final String CAR_SERVICE_PACKAGE = "com.android.car";
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ getConnectionListener().onServiceConnected(name, service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // unbind explicitly here.
+ disconnect();
+ getConnectionListener().onServiceDisconnected(name);
+ }
+ };
+
+ public DefaultCarServiceLoader(Context context, ServiceConnectionListener listener) {
+ super(context, listener);
+ }
+
+ @Override
+ public void connect() throws IllegalStateException {
+ startCarService();
+ }
+
+ @Override
+ public void disconnect() {
+ getContext().unbindService(mServiceConnection);
+ }
+
+ private void startCarService() {
+ Intent intent = new Intent();
+ intent.setPackage(CAR_SERVICE_PACKAGE);
+ intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
+ getContext().startService(intent);
+ getContext().bindService(intent, mServiceConnection, 0);
+ }
+}
diff --git a/carsupport-lib/src/android/support/car/ICar.aidl b/carsupport-lib/src/android/support/car/ICar.aidl
new file mode 100644
index 0000000000..466906d321
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/ICar.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.Intent;
+//import android.content.pm.ResolveInfo;
+
+import android.support.car.CarInfo;
+import android.support.car.CarUiInfo;
+import android.support.car.ICarConnectionListener;
+
+/** @hide */
+interface ICar {
+ int getVersion() = 0;
+ IBinder getCarService(in String serviceName) = 1;
+ CarInfo getCarInfo() = 2;
+ CarUiInfo getCarUiInfo() = 3;
+ boolean isConnectedToCar() = 4;
+ int getCarConnectionType() = 5;
+ void registerCarConnectionListener(int clientVersion, in ICarConnectionListener listener) = 6;
+ void unregisterCarConnectionListener(in ICarConnectionListener listener) = 7;
+ boolean startCarActivity(in Intent intent) = 8;
+ /* TODO
+ List<ResolveInfo> queryIntentCarProjectionServices(in Intent intent) = 9;
+ List<ResolveInfo> queryAllowedServices(in Intent intent, int applicationType) = 10;
+ boolean isPackageAllowed(in String packageName, int applicationType) = 11;*/
+}
diff --git a/carsupport-lib/src/android/support/car/ICarConnectionListener.aidl b/carsupport-lib/src/android/support/car/ICarConnectionListener.aidl
new file mode 100644
index 0000000000..f638114e0e
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/ICarConnectionListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Binder callback for CarConnectionListener.
+ * @hide
+ */
+oneway interface ICarConnectionListener {
+ void onConnected(int connectionType);
+ void onDisconnected();
+}
diff --git a/carsupport-lib/src/android/support/car/ICarSensor.aidl b/carsupport-lib/src/android/support/car/ICarSensor.aidl
new file mode 100644
index 0000000000..b82d8e6193
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/ICarSensor.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.support.car.CarSensorEvent;
+import android.support.car.ICarSensorEventListener;
+
+/** @hide */
+interface ICarSensor {
+ int getVersion() = 0;
+
+ int[] getSupportedSensors() = 1;
+
+ /**
+ * register a listener or update registration if already updated.
+ * @param sensorType sensor to listen with this listener.
+ * @param rate sensor rate.
+ * @return false if requested sensors cannot be subscribed / started.
+ */
+ boolean registerOrUpdateSensorListener(int sensorType, int rate, int clientVersion,
+ in ICarSensorEventListener listener) = 2;
+
+ /**
+ * get latest sensor event for the type. If there was no update after car connection, it will
+ * return null immediately.
+ */
+ CarSensorEvent getLatestSensorEvent(int sensorType) = 3;
+
+ /**
+ * Stop listening for the given sensor type. All other sensors registered before will not
+ * be affected.
+ */
+ void unregisterSensorListener(int sensorType, in ICarSensorEventListener listener) = 4;
+}
diff --git a/carsupport-lib/src/android/support/car/ICarSensorEventListener.aidl b/carsupport-lib/src/android/support/car/ICarSensorEventListener.aidl
new file mode 100644
index 0000000000..178572ce47
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/ICarSensorEventListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.support.car.CarSensorEvent;
+
+/**
+ * Binder callback for CarSensorEventListener.
+ * This is generated per each CarClient.
+ * @hide
+ */
+oneway interface ICarSensorEventListener {
+ void onSensorChanged(in CarSensorEvent event) = 0;
+}
diff --git a/carsupport-lib/src/android/support/car/ServiceConnectionListener.java b/carsupport-lib/src/android/support/car/ServiceConnectionListener.java
new file mode 100644
index 0000000000..bea7057ea6
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/ServiceConnectionListener.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ComponentName;
+import android.os.IBinder;
+
+/**
+ * Listener for service connection related events.
+ */
+public interface ServiceConnectionListener {
+ void onServiceConnected(ComponentName name, IBinder service);
+ void onServiceDisconnected(ComponentName name);
+ //TODO define cause values
+ void onServiceSuspended(int cause);
+ //TODO define cause values
+ void onServiceConnectionFailed(int cause);
+}
diff --git a/carsupport-lib/src/android/support/car/app/CarActivity.java b/carsupport-lib/src/android/support/car/app/CarActivity.java
new file mode 100644
index 0000000000..27a98dde72
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/app/CarActivity.java
@@ -0,0 +1,166 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.car.Car;
+import android.support.car.CarLibLog;
+import android.support.car.CarNotConnectedException;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * Abstraction for car UI. Car applications should implement this for car UI.
+ * The API looks like {@link android.app.Activity}, and behaves like one, but it is neither
+ * android {@link android.app.Activity} nor {@link android.app.Context}.
+ * Applications should use {@link #getContext()} to get necessary {@link android.app.Context}.
+ * To use car API, {@link #getCarApi()} can be used. The {@link Car} api passed is already
+ * connected, and client does not need to call {@link Car#connect()}.
+ */
+public abstract class CarActivity {
+
+ /**
+ * Interface to connect {@link CarActivity} to {@link android.app.Activity} or other app model.
+ * This interface provides utility for {@link CarActivity} to do things like manipulating view,
+ * handling menu, and etc.
+ */
+ public interface Proxy {
+ void setContentView(View view);
+ void setContentView(int layoutResID);
+ View findViewById(int id);
+ void finish();
+ }
+
+ /** @hide */
+ static final int CMD_ON_CREATE = 0;
+ /** @hide */
+ static final int CMD_ON_START = 1;
+ /** @hide */
+ static final int CMD_ON_RESTART = 2;
+ /** @hide */
+ static final int CMD_ON_RESUME = 3;
+ /** @hide */
+ static final int CMD_ON_PAUSE = 4;
+ /** @hide */
+ static final int CMD_ON_STOP = 5;
+ /** @hide */
+ static final int CMD_ON_DESTROY = 6;
+
+ private final Proxy mProxy;
+ private final Context mContext;
+ private final Car mCarApi;
+
+ public CarActivity(Proxy proxy, Context context, Car carApi) {
+ mProxy = proxy;
+ mContext = context;
+ mCarApi = carApi;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Car getCarApi() {
+ return mCarApi;
+ }
+
+ /**
+ * Check {@link android.app.Activity#setContentView(View)}.
+ * @param view
+ */
+ public void setContentView(View view) {
+ mProxy.setContentView(view);
+ }
+
+ public void setContentView(int layoutResID) {
+ mProxy.setContentView(layoutResID);
+ }
+
+ public View findViewById(int id) {
+ return mProxy.findViewById(id);
+ }
+
+ public void finish() {
+ mProxy.finish();
+ }
+
+ /** @hide */
+ void dispatchCmd(int cmd, Object arg0) {
+ try {
+ switch (cmd) {
+ case CMD_ON_CREATE:
+ onCreate((Bundle) arg0);
+ break;
+ case CMD_ON_START:
+ onStart();
+ break;
+ case CMD_ON_RESTART:
+ onRestart();
+ break;
+ case CMD_ON_RESUME:
+ onResume();
+ break;
+ case CMD_ON_PAUSE:
+ onPause();
+ break;
+ case CMD_ON_STOP:
+ onStop();
+ break;
+ case CMD_ON_DESTROY:
+ onDestroy();
+ break;
+ default:
+ throw new RuntimeException("Unknown dispatch cmd for CarActivity, " + cmd);
+ }
+ } catch (CarNotConnectedException e) {
+ Log.w(CarLibLog.TAG_CAR, "Finish CarActivity due to exception ", e);
+ if (cmd != CMD_ON_DESTROY) {
+ finish();
+ }
+ }
+ }
+
+ protected void onCreate(Bundle savedInstanceState)
+ throws CarNotConnectedException {
+ }
+
+ protected void onStart()
+ throws CarNotConnectedException {
+ }
+
+ protected void onRestart()
+ throws CarNotConnectedException {
+ }
+
+ protected void onResume()
+ throws CarNotConnectedException {
+ }
+
+ protected void onPause()
+ throws CarNotConnectedException {
+ }
+
+ protected void onStop()
+ throws CarNotConnectedException {
+ }
+
+ protected void onDestroy()
+ throws CarNotConnectedException {
+ }
+
+}
diff --git a/carsupport-lib/src/android/support/car/app/CarProxyActivity.java b/carsupport-lib/src/android/support/car/app/CarProxyActivity.java
new file mode 100644
index 0000000000..b8db7bed1b
--- /dev/null
+++ b/carsupport-lib/src/android/support/car/app/CarProxyActivity.java
@@ -0,0 +1,190 @@
+/*
+ * 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.app;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.support.car.Car;
+import android.support.car.ServiceConnectionListener;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.LinkedList;
+
+/**
+ * android Activity controlling / proxying {@link CarActivity}. Applications should have its own
+ * {@link android.app.Activity} overriding only constructor.
+ */
+public class CarProxyActivity extends Activity {
+
+ private final Class mCarActivityClass;
+ // no synchronization, but main thread only
+ private Car mCarApi;
+ // no synchronization, but main thread only
+ private CarActivity mCarActivity;
+ @GuardedBy("this")
+ private boolean mConnected = false;
+ @GuardedBy("this")
+ private final LinkedList<Pair<Integer, Object>> mCmds = new LinkedList<Pair<Integer, Object>>();
+ private final ServiceConnectionListener mConnectionListener= new ServiceConnectionListener() {
+
+ @Override
+ public void onServiceSuspended(int cause) {
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onServiceConnectionFailed(int cause) {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mConnected = true;
+ for (Pair<Integer, Object> cmd: mCmds) {
+ mCarActivity.dispatchCmd(cmd.first, cmd.second);
+ }
+ mCmds.clear();
+ }
+ }
+ };
+
+ private final CarActivity.Proxy mCarActivityProxy = new CarActivity.Proxy() {
+ @Override
+ public void setContentView(View view) {
+ CarProxyActivity.this.setContentView(view);
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ CarProxyActivity.this.setContentView(layoutResID);
+ }
+
+ @Override
+ public View findViewById(int id) {
+ return CarProxyActivity.this.findViewById(id);
+ }
+
+ @Override
+ public void finish() {
+ CarProxyActivity.this.finish();
+ }
+
+ };
+
+ public CarProxyActivity(Class carActivityClass) {
+ mCarActivityClass = carActivityClass;
+ }
+
+ private void createCarActivity() {
+ mCarApi = new Car(this, mConnectionListener, null);
+ mCarApi.connect();
+ Constructor<?> ctor;
+ try {
+ ctor = mCarActivityClass.getDeclaredConstructor(CarActivity.Proxy.class, Context.class,
+ Car.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Cannot construct given CarActivity, no constructor for " +
+ mCarActivityClass.getName(), e);
+ }
+ try {
+ mCarActivity = (CarActivity) ctor.newInstance(mCarActivityProxy, this, mCarApi);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ throw new RuntimeException("Cannot construct given CarActivity, constructor failed for "
+ + mCarActivityClass.getName(), e);
+ }
+ }
+
+ private synchronized boolean isConnected() {
+ return mConnected;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ createCarActivity();
+ super.onCreate(savedInstanceState);
+ handleCmd(CarActivity.CMD_ON_CREATE, savedInstanceState);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ handleCmd(CarActivity.CMD_ON_START, null);
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ handleCmd(CarActivity.CMD_ON_RESTART, null);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ handleCmd(CarActivity.CMD_ON_RESUME, null);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ handleCmd(CarActivity.CMD_ON_PAUSE, null);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ handleCmd(CarActivity.CMD_ON_STOP, null);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (!isConnected()) {
+ // not connected yet, so even onCreate was not called. Do not pass to CarActivity.
+ mCarApi.disconnect();
+ return;
+ }
+ handleCmd(CarActivity.CMD_ON_DESTROY, null);
+ // disconnect last so that car api is valid in onDestroy call.
+ mCarApi.disconnect();
+ }
+
+ private void handleCmd(int cmd, Object arg0) {
+ if (isConnected()) {
+ mCarActivity.dispatchCmd(cmd, arg0);
+ } else {
+ // not connected yet. queue it and return.
+ Pair<Integer, Object> cmdToQ = new Pair<Integer, Object>(Integer.valueOf(cmd), arg0);
+ synchronized (this) {
+ mCmds.add(cmdToQ);
+ }
+ }
+ }
+}
diff --git a/service/Android.mk b/service/Android.mk
new file mode 100644
index 0000000000..c9fa9e1221
--- /dev/null
+++ b/service/Android.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+#
+
+# Build the Car service.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CarService
+
+# Each update should be signed by OEMs
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+#LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES += libcarsupport
+
+include $(BUILD_PACKAGE)
+
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
new file mode 100644
index 0000000000..957160cf65
--- /dev/null
+++ b/service/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.car"
+ coreApp="true"
+ android:sharedUserId="android.uid.system">
+
+ <original-package android:name="com.android.car" />
+
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.REBOOT" />
+
+ <application android:label="Car service"
+ android:allowBackup="false"
+ android:persistent="true">
+
+ <service android:name=".CarService"
+ android:singleUser="true">
+ <intent-filter>
+ <action android:name="android.support.car.ICar" />
+ </intent-filter>
+ </service>
+ <receiver android:name=".BootReceiver">
+ <intent-filter android:priority="1000">
+ <action android:name="android.intent.action.PRE_BOOT_COMPLETED"/>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/service/proguard.flags b/service/proguard.flags
new file mode 100644
index 0000000000..22cc22df14
--- /dev/null
+++ b/service/proguard.flags
@@ -0,0 +1,3 @@
+-verbose
+-keep @com.android.internal.annotations.VisibleForTesting class *
+
diff --git a/service/src/com/android/car/BootReceiver.java b/service/src/com/android/car/BootReceiver.java
new file mode 100644
index 0000000000..5bfa96c3a1
--- /dev/null
+++ b/service/src/com/android/car/BootReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.car;
+
+import android.os.UserHandle;
+import android.support.car.Car;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.car.hal.Hal;
+
+
+/**
+ * When system boots up, start car service.
+ */
+public class BootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.w(CarLog.TAG_SERVICE, "Starting...");
+ Hal hal = Hal.getInstance(context.getApplicationContext());
+ Intent carServiceintent = new Intent();
+ carServiceintent.setPackage(context.getPackageName());
+ carServiceintent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
+ context.startServiceAsUser(carServiceintent, new UserHandle(0));
+ }
+}
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
new file mode 100644
index 0000000000..f8bdff9c9d
--- /dev/null
+++ b/service/src/com/android/car/CarLog.java
@@ -0,0 +1,23 @@
+/*
+ * 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 com.android.car;
+
+public class CarLog {
+
+ public static final String TAG_SERVICE = "CAR.SERVICE";
+ public static final String TAG_SENSOR = "CAR.SENSOR";
+}
diff --git a/service/src/com/android/car/CarSensorService.java b/service/src/com/android/car/CarSensorService.java
new file mode 100644
index 0000000000..cd899af68d
--- /dev/null
+++ b/service/src/com/android/car/CarSensorService.java
@@ -0,0 +1,833 @@
+/*
+ * 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 com.android.car;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.car.Car;
+import android.support.car.CarSensorEvent;
+import android.support.car.CarSensorManager;
+import android.support.car.ICarSensor;
+import android.support.car.ICarSensorEventListener;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.car.hal.Hal;
+import com.android.car.hal.SensorHal;
+import com.android.car.hal.SensorHalBase;
+import com.android.car.hal.SensorHalBase.SensorListener;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.ConcurrentModificationException;
+import java.util.LinkedList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+public class CarSensorService extends ICarSensor.Stub
+ implements CarServiceBase, SensorHal.SensorListener {
+
+ /**
+ * Abstraction for logical sensor which is not physical sensor but presented as sensor to
+ * upper layer. Currently {@link CarSensorManager#SENSOR_TYPE_NIGHT} and
+ * {@link CarSensorManager#SENSOR_TYPE_DRIVING_STATUS} falls into this category.
+ * Implementation can call {@link CarSensorService#onSensorData(CarSensorEvent)} when there
+ * is state change for the given sensor after {@link SensorHalBase#init()}
+ * is called.
+ */
+ public static abstract class LogicalSensorHalBase extends SensorHalBase {
+
+ /** Sensor service is ready and all vehicle sensors are available. */
+ public abstract void onSensorServiceReady();
+
+ /**
+ * Provide default sensor value. This value should be always provided after {@link #init()}
+ * call. Default value for safety related sensor should be giving safe state.
+ * @param sensorType
+ * @return
+ */
+ public abstract CarSensorEvent getDefaultValue(int sensorType);
+ }
+
+ static final int VERSION = 1;
+
+ /** {@link #mSensorLock} is not waited forever for handling disconnection */
+ private static final long MAX_SENSOR_LOCK_WAIT_MS = 1000;
+
+ /** lock to access sensor structures */
+ private final ReentrantLock mSensorLock = new ReentrantLock();
+ /** hold clients callback */
+ @GuardedBy("mSensorLock")
+ private final LinkedList<SensorClient> mClients = new LinkedList<SensorClient>();
+ /** key: sensor type. */
+ @GuardedBy("mSensorLock")
+ private final SparseArray<SensorListeners> mSensorListeners = new SparseArray<>();
+ /** key: sensor type. */
+ @GuardedBy("mSensorLock")
+ private final SparseArray<SensorRecord> mSensorRecords = new SparseArray<>();
+
+ private final SensorHal mSensorHal;
+ private int[] mCarProvidedSensors;
+ private int[] mSupportedSensors;
+ private final AtomicBoolean mSensorDiscovered = new AtomicBoolean(false);
+
+ private final Context mContext;
+
+ private final DrivingStatePolicy mDrivingStatePolicy;
+ private final DayNightModePolicy mDayNightModePolicy;
+
+ public CarSensorService(Context context) {
+ mContext = context;
+ // This triggers sensor hal init as well.
+ mSensorHal = Hal.getInstance(context.getApplicationContext()).getSensorHal();
+ mDrivingStatePolicy = new DrivingStatePolicy(context);
+ mDayNightModePolicy = new DayNightModePolicy(context);
+ }
+
+ @Override
+ public void init() {
+ // Watch out the order. registerSensorListener can lead into onSensorHalReady call.
+ // So it should be done last.
+ mDrivingStatePolicy.init();
+ mDayNightModePolicy.init();
+ mSensorLock.lock();
+ try {
+ mSupportedSensors = refreshSupportedSensorsLocked();
+ addNewSensorRecordLocked(CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
+ mDrivingStatePolicy.getDefaultValue(
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS));
+ addNewSensorRecordLocked(CarSensorManager.SENSOR_TYPE_NIGHT,
+ mDrivingStatePolicy.getDefaultValue(
+ CarSensorManager.SENSOR_TYPE_NIGHT));
+
+ } finally {
+ mSensorLock.unlock();
+ }
+ mDrivingStatePolicy.registerSensorListener(this);
+ mDayNightModePolicy.registerSensorListener(this);
+ mSensorHal.registerSensorListener(this);
+ }
+
+ private void addNewSensorRecordLocked(int type, CarSensorEvent event) {
+ SensorRecord record = new SensorRecord();
+ record.lastEvent = event;
+ mSensorRecords.put(type,record);
+ }
+
+ @Override
+ public void release() {
+ mDrivingStatePolicy.release();
+ mDayNightModePolicy.release();
+ tryHoldSensorLock();
+ try {
+ for (int i = mSensorListeners.size() - 1; i >= 0; --i) {
+ SensorListeners listener = mSensorListeners.valueAt(i);
+ listener.release();
+ }
+ mSensorListeners.clear();
+ mSensorRecords.clear();
+ mClients.clear();
+ } finally {
+ releaseSensorLockSafely();
+ }
+ }
+
+ private void tryHoldSensorLock() {
+ try {
+ mSensorLock.tryLock(MAX_SENSOR_LOCK_WAIT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+
+ private void releaseSensorLockSafely() {
+ if (mSensorLock.isHeldByCurrentThread()) {
+ mSensorLock.unlock();
+ }
+ }
+
+ @Override
+ public void onSensorHalReady(SensorHalBase hal) {
+ if (hal == mSensorHal) {
+ mCarProvidedSensors = mSensorHal.getSupportedSensors();
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.VERBOSE)) {
+ Log.v(CarLog.TAG_SENSOR, "sensor Hal ready, available sensors:" +
+ Arrays.toString(mCarProvidedSensors));
+ }
+ mSensorLock.lock();
+ try {
+ mSupportedSensors = refreshSupportedSensorsLocked();
+ } finally {
+ mSensorLock.unlock();
+ }
+ mDrivingStatePolicy.onSensorServiceReady();
+ mDayNightModePolicy.onSensorServiceReady();
+ }
+ }
+
+ private void processSensorDataLocked(CarSensorEvent event) {
+ SensorRecord record = mSensorRecords.get(event.sensorType);
+ if (record != null) {
+ record.lastEvent = event;
+ } else {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "sensor data received without matching record");
+ }
+ }
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.VERBOSE)) {
+ Log.v(CarLog.TAG_SENSOR, "onSensorData type: " + event.sensorType);
+ }
+ notifySensorEventToClientLocked(event, event.sensorType);
+ }
+
+ /**
+ * Received sensor data from car.
+ *
+ * @param event
+ */
+ @Override
+ public void onSensorData(CarSensorEvent event) {
+ mSensorLock.lock();
+ try {
+ processSensorDataLocked(event);
+ } finally {
+ mSensorLock.unlock();
+ }
+ }
+
+ @Override
+ public int[] getSupportedSensors() {
+ return mSupportedSensors;
+ }
+
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public boolean registerOrUpdateSensorListener(int sensorType, int rate,
+ int clientVersion, ICarSensorEventListener listener) {
+ boolean shouldStartSensors = false;
+ SensorRecord sensorRecord = null;
+ SensorClient sensorClient = null;
+ Integer oldRate = null;
+ SensorListeners sensorListeners = null;
+ mSensorLock.lock();
+ try {
+ sensorRecord = mSensorRecords.get(sensorType);
+ if (sensorRecord == null) {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.INFO)) {
+ Log.i(CarLog.TAG_SENSOR, "Requested sensor " + sensorType + " not supported");
+ }
+ return false;
+ }
+ if (Binder.getCallingUid() != Process.myUid()) {
+ switch (getSensorPermission(sensorType)) {
+ case PackageManager.PERMISSION_DENIED:
+ throw new SecurityException("client does not have permission:"
+ + getPermissionName(sensorType)
+ + " pid:" + Binder.getCallingPid()
+ + " uid:" + Binder.getCallingUid());
+ case PackageManager.PERMISSION_GRANTED:
+ break;
+ }
+ }
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "registerOrUpdateSensorListener " + sensorType + " " +
+ listener);
+ }
+ sensorClient = findSensorClientLocked(listener);
+ SensorClientWithRate sensorClientWithRate = null;
+ sensorListeners = mSensorListeners.get(sensorType);
+ if (sensorClient == null) {
+ sensorClient = new SensorClient(listener);
+ try {
+ listener.asBinder().linkToDeath(sensorClient, 0);
+ } catch (RemoteException e) {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.INFO)) {
+ Log.i(CarLog.TAG_SENSOR, "Adding listener failed.");
+ }
+ return false;
+ }
+ mClients.add(sensorClient);
+ }
+ // If we have a cached event for this sensor, send the event.
+ SensorRecord record = mSensorRecords.get(sensorType);
+ if (record != null && record.lastEvent != null) {
+ sensorClient.onSensorUpdate(record.lastEvent);
+ }
+ if (sensorListeners == null) {
+ sensorListeners = new SensorListeners(rate);
+ mSensorListeners.put(sensorType, sensorListeners);
+ shouldStartSensors = isSensorRealLocked(sensorType);
+ } else {
+ oldRate = Integer.valueOf(sensorListeners.getRate());
+ sensorClientWithRate = sensorListeners.findSensorClientWithRate(sensorClient);
+ }
+ if (sensorClientWithRate == null) {
+ sensorClientWithRate = new SensorClientWithRate(sensorClient, rate);
+ sensorListeners.addSensorClientWithRate(sensorClientWithRate);
+ } else {
+ sensorClientWithRate.setRate(rate);
+ }
+ if (sensorListeners.getRate() > rate) {
+ sensorListeners.setRate(rate);
+ shouldStartSensors = isSensorRealLocked(sensorType);
+ }
+ sensorClient.addSensor(sensorType);
+ } finally {
+ mSensorLock.unlock();
+ }
+ // start sensor outside lock as it can take time.
+ if (shouldStartSensors) {
+ if (!startSensor(sensorRecord, sensorType, rate)) {
+ // failed. so remove from active sensor list.
+ mSensorLock.lock();
+ try {
+ sensorClient.removeSensor(sensorType);
+ if (oldRate != null) {
+ sensorListeners.setRate(oldRate);
+ } else {
+ mSensorListeners.remove(sensorType);
+ }
+ } finally {
+ mSensorLock.unlock();
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int getSensorPermission(int sensorType) {
+ String permission = getPermissionName(sensorType);
+ int result = PackageManager.PERMISSION_GRANTED;
+ if (permission != null) {
+ /*TODO
+ result = CarServiceUtils.checkCallingPermission(
+ mContext, permission);*/
+ }
+ // If no permission is required, return granted.
+ return result;
+ }
+
+ private String getPermissionName(int sensorType) {
+ String permission = null;
+ switch (sensorType) {
+ case CarSensorManager.SENSOR_TYPE_LOCATION:
+ permission = Manifest.permission.ACCESS_FINE_LOCATION;
+ break;
+ case CarSensorManager.SENSOR_TYPE_CAR_SPEED:
+ permission = Car.PERMISSION_SPEED;
+ break;
+ case CarSensorManager.SENSOR_TYPE_ODOMETER:
+ permission = Car.PERMISSION_MILEAGE;
+ break;
+ case CarSensorManager.SENSOR_TYPE_FUEL_LEVEL:
+ permission = Car.PERMISSION_FUEL;
+ break;
+ default:
+ break;
+ }
+ return permission;
+ }
+
+ private boolean startSensor(SensorRecord record, int sensorType, int rate) {
+ //TODO choose proper sensor rate per each sensor.
+ //Some sensors which report only when there is change should be always set with maximum
+ //rate. For now, set every sensor to the maximum.
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.VERBOSE)) {
+ Log.v(CarLog.TAG_SENSOR, "startSensor " + sensorType + " with rate " + rate);
+ }
+ SensorHalBase sensorHal = getSensorHal(sensorType);
+ if (sensorHal != null) {
+ if (!sensorHal.isReady()) {
+ Log.w(CarLog.TAG_SENSOR, "Sensor channel not available.");
+ return false;
+ }
+ if (record.enabled) {
+ return true;
+ }
+ if (sensorHal.requestSensorStart(sensorType, 0)) {
+ record.enabled = true;
+ return true;
+ }
+ }
+ Log.w(CarLog.TAG_SENSOR, "requestSensorStart failed");
+ return false;
+ }
+
+ @Override
+ public void unregisterSensorListener(int sensorType, ICarSensorEventListener listener) {
+ boolean shouldStopSensor = false;
+ boolean shouldRestartSensor = false;
+ SensorRecord record = null;
+ int newRate = 0;
+ mSensorLock.lock();
+ try {
+ record = mSensorRecords.get(sensorType);
+ if (record == null) {
+ // unregister not supported sensor. ignore.
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "unregister for unsupported sensor");
+ }
+ return;
+ }
+ SensorClient sensorClient = findSensorClientLocked(listener);
+ if (sensorClient == null) {
+ // never registered or already unregistered.
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "unregister for not existing client");
+ }
+ return;
+ }
+ sensorClient.removeSensor(sensorType);
+ if (sensorClient.getNumberOfActiveSensor() == 0) {
+ sensorClient.release();
+ mClients.remove(sensorClient);
+ }
+ SensorListeners sensorListeners = mSensorListeners.get(sensorType);
+ if (sensorListeners == null) {
+ // sensor not active
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "unregister for non-active sensor");
+ }
+ return;
+ }
+ SensorClientWithRate clientWithRate =
+ sensorListeners.findSensorClientWithRate(sensorClient);
+ if (clientWithRate == null) {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "unregister for not registered sensor");
+ }
+ return;
+ }
+ sensorListeners.removeSensorClientWithRate(clientWithRate);
+ if (sensorListeners.getNumberOfClients() == 0) {
+ shouldStopSensor = isSensorRealLocked(sensorType);
+ mSensorListeners.remove(sensorType);
+ } else if (sensorListeners.updateRate()) { // rate changed
+ newRate = sensorListeners.getRate();
+ shouldRestartSensor = isSensorRealLocked(sensorType);
+ }
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "unregister succeeded");
+ }
+ } finally {
+ mSensorLock.unlock();
+ }
+ if (shouldStopSensor) {
+ stopSensor(record, sensorType);
+ } else if (shouldRestartSensor) {
+ startSensor(record, sensorType, newRate);
+ }
+ }
+
+ private void stopSensor(SensorRecord record, int sensorType) {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "stopSensor " + sensorType);
+ }
+ SensorHalBase sensorHal = getSensorHal(sensorType);
+ if (sensorHal == null || !sensorHal.isReady()) {
+ Log.w(CarLog.TAG_SENSOR, "Sensor channel not available.");
+ return;
+ }
+ if (!record.enabled) {
+ return;
+ }
+ record.enabled = false;
+ // make lastEvent invalid as old data can be sent to client when subscription is restarted
+ // later.
+ record.lastEvent = null;
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "stopSensor requestStop " + sensorType);
+ }
+ sensorHal.requestSensorStop(sensorType);
+ }
+
+ private SensorHalBase getSensorHal(int sensorType) {
+ switch (sensorType) {
+ case CarSensorManager.SENSOR_TYPE_DRIVING_STATUS:
+ return mDrivingStatePolicy;
+ case CarSensorManager.SENSOR_TYPE_NIGHT:
+ return mDayNightModePolicy;
+ default:
+ return mSensorHal;
+ }
+ }
+
+ @Override
+ public CarSensorEvent getLatestSensorEvent(int sensorType) {
+ SensorRecord record = null;
+ mSensorLock.lock();
+ try {
+ record = mSensorRecords.get(sensorType);
+ } finally {
+ mSensorLock.unlock();
+ }
+ if (record != null) {
+ return record.lastEvent;
+ }
+ return null;
+ }
+
+ private int[] refreshSupportedSensorsLocked() {
+ int numCarSensors = (mCarProvidedSensors == null) ? 0 : mCarProvidedSensors.length;
+ // Two logical sensors are always added.
+ int[] supportedSensors = new int[numCarSensors + 2];
+ int index = 0;
+ supportedSensors[index] = CarSensorManager.SENSOR_TYPE_DRIVING_STATUS;
+ index++;
+ supportedSensors[index] = CarSensorManager.SENSOR_TYPE_NIGHT;
+ index++;
+
+ for (int i = 0; i < numCarSensors; i++) {
+ int sensor = mCarProvidedSensors[i];
+ if (mSensorRecords.get(sensor) == null) {
+ SensorRecord record = new SensorRecord();
+ mSensorRecords.put(sensor, record);
+ }
+ supportedSensors[index] = sensor;
+ index++;
+ }
+
+ return supportedSensors;
+ }
+
+ private boolean isSensorRealLocked(int sensorType) {
+ if (mCarProvidedSensors != null) {
+ for (int sensor : mCarProvidedSensors) {
+ if (sensor == sensorType ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void notifySensorEventToClientLocked(CarSensorEvent event, int sensor) {
+ SensorListeners listeners = mSensorListeners.get(sensor);
+ if (listeners != null) {
+ listeners.onSensorUpdate(event);
+ } else {
+ Log.w(CarLog.TAG_SENSOR, "sensor event while no listener, sensor:" + sensor);
+ }
+ }
+
+ /**
+ * Find SensorClient from client list and return it.
+ * This should be called with mClients locked.
+ * @param listener
+ * @return null if not found.
+ */
+ private SensorClient findSensorClientLocked(ICarSensorEventListener listener) {
+ IBinder binder = listener.asBinder();
+ for (SensorClient sensorClient : mClients) {
+ if (sensorClient.isHoldingListernerBinder(binder)) {
+ return sensorClient;
+ }
+ }
+ return null;
+ }
+
+ private void removeClient(SensorClient sensorClient) {
+ mSensorLock.lock();
+ try {
+ for (int sensor: sensorClient.getSensorArray()) {
+ unregisterSensorListener(sensor,
+ sensorClient.getICarSensorEventListener());
+ }
+ mClients.remove(sensorClient);
+ } finally {
+ mSensorLock.unlock();
+ }
+ }
+
+ /** internal instance for pending client request */
+ private class SensorClient implements IBinder.DeathRecipient {
+ /** callback for sensor events */
+ private final ICarSensorEventListener mListener;
+ private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
+
+ /** when false, it is already released */
+ private volatile boolean mActive = true;
+
+ SensorClient(ICarSensorEventListener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SensorClient &&
+ mListener.asBinder() == ((SensorClient) o).mListener.asBinder()) {
+ return true;
+ }
+ return false;
+ }
+
+ boolean isHoldingListernerBinder(IBinder listenerBinder) {
+ return mListener.asBinder() == listenerBinder;
+ }
+
+ void addSensor(int sensor) {
+ mActiveSensors.put(sensor, true);
+ }
+
+ void removeSensor(int sensor) {
+ mActiveSensors.delete(sensor);
+ }
+
+ int getNumberOfActiveSensor() {
+ return mActiveSensors.size();
+ }
+
+ int[] getSensorArray() {
+ int[] sensors = new int[mActiveSensors.size()];
+ for (int i = sensors.length - 1; i >= 0; --i) {
+ sensors[i] = mActiveSensors.keyAt(i);
+ }
+ return sensors;
+ }
+
+ ICarSensorEventListener getICarSensorEventListener() {
+ return mListener;
+ }
+
+ /**
+ * Client dead. should remove all sensor requests from client
+ */
+ @Override
+ public void binderDied() {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ removeClient(this);
+ }
+
+ void onSensorUpdate(CarSensorEvent event) {
+ try {
+ if (mActive) {
+ mListener.onSensorChanged(event);
+ } else {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.DEBUG)) {
+ Log.d(CarLog.TAG_SENSOR, "sensor update while client is already released");
+ }
+ }
+ } catch (RemoteException e) {
+ //ignore. crash will be handled by death handler
+ }
+ }
+
+ void release() {
+ if (mActive) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ mActiveSensors.clear();
+ mActive = false;
+ }
+ }
+ }
+
+ private class SensorClientWithRate {
+ private final SensorClient mSensorClient;
+ /** rate requested from client */
+ private int mRate;
+
+ SensorClientWithRate(SensorClient client, int rate) {
+ mSensorClient = client;
+ mRate = rate;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof SensorClientWithRate &&
+ mSensorClient == ((SensorClientWithRate) o).mSensorClient) {
+ return true;
+ }
+ return false;
+ }
+
+ int getRate() {
+ return mRate;
+ }
+
+ void setRate(int rate) {
+ mRate = rate;
+ }
+
+ SensorClient getSensorClient() {
+ return mSensorClient;
+ }
+ }
+
+ private static class SensorRecord {
+ /** Record the lastly received sensor event */
+ CarSensorEvent lastEvent = null;
+ /** sensor was enabled by at least one client */
+ boolean enabled = false;
+ }
+
+ private static class SensorListeners {
+ private final LinkedList<SensorClientWithRate> mSensorClients =
+ new LinkedList<SensorClientWithRate>();
+ /** rate for this sensor, sent to car */
+ private int mRate;
+
+ SensorListeners(int rate) {
+ mRate = rate;
+ }
+
+ int getRate() {
+ return mRate;
+ }
+
+ void setRate(int rate) {
+ mRate = rate;
+ }
+
+ /** update rate from existing clients and return true if rate is changed. */
+ boolean updateRate() {
+ int fastestRate = CarSensorManager.SENSOR_RATE_NORMAL;
+ for (SensorClientWithRate clientWithRate: mSensorClients) {
+ int clientRate = clientWithRate.getRate();
+ if (clientRate < fastestRate) {
+ fastestRate = clientRate;
+ }
+ }
+ if (mRate != fastestRate) {
+ mRate = fastestRate;
+ return true;
+ }
+ return false;
+ }
+
+ void addSensorClientWithRate(SensorClientWithRate clientWithRate) {
+ mSensorClients.add(clientWithRate);
+ }
+
+ void removeSensorClientWithRate(SensorClientWithRate clientWithRate) {
+ mSensorClients.remove(clientWithRate);
+ }
+
+ int getNumberOfClients() {
+ return mSensorClients.size();
+ }
+
+ SensorClientWithRate findSensorClientWithRate(SensorClient sensorClient) {
+ for (SensorClientWithRate clientWithRates: mSensorClients) {
+ if (clientWithRates.getSensorClient() == sensorClient) {
+ return clientWithRates;
+ }
+ }
+ return null;
+ }
+
+ void onSensorUpdate(CarSensorEvent event) {
+ if (Log.isLoggable(CarLog.TAG_SENSOR, Log.VERBOSE)) {
+ Log.v(CarLog.TAG_SENSOR, "onSensorUpdate to clients: " + mSensorClients.size());
+ }
+ for (SensorClientWithRate clientWithRate: mSensorClients) {
+ clientWithRate.getSensorClient().onSensorUpdate(event);
+ }
+ }
+
+ void release() {
+ for (SensorClientWithRate clientWithRate: mSensorClients) {
+ clientWithRate.mSensorClient.release();
+ }
+ mSensorClients.clear();
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("supported sensors:" + Arrays.toString(mSupportedSensors));
+ writer.println("**last events for sensors**");
+ if (mSensorRecords != null) {
+ try {
+ int sensorRecordSize = mSensorRecords.size();
+ for (int i = 0; i < sensorRecordSize; i++) {
+ int sensor = mSensorRecords.keyAt(i);
+ SensorRecord record = mSensorRecords.get(sensor);
+ if (record != null && record.lastEvent != null) {
+ writer.println("sensor: " + sensor
+ + " active: " + record.enabled);
+ // only print sensor data which is not related with location
+ if (sensor != CarSensorManager.SENSOR_TYPE_LOCATION &&
+ sensor != CarSensorManager.SENSOR_TYPE_DEAD_RECKONING &&
+ sensor != CarSensorManager.SENSOR_TYPE_GPS_SATELLITE) {
+ writer.println(" " + record.lastEvent.toString());
+ }
+ }
+ SensorListeners listeners = mSensorListeners.get(sensor);
+ if (listeners != null) {
+ writer.println(" rate: " + listeners.getRate());
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ } else {
+ writer.println("null records");
+ }
+ writer.println("**clients**");
+ try {
+ for (SensorClient client: mClients) {
+ if (client != null) {
+ try {
+ writer.println("binder:" + client.mListener
+ + " active sensors:" + Arrays.toString(client.getSensorArray()));
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ } else {
+ writer.println("null client");
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ writer.println("**sensor listeners**");
+ try {
+ int sensorListenerSize = mSensorListeners.size();
+ for (int i = 0; i < sensorListenerSize; i++) {
+ int sensor = mSensorListeners.keyAt(i);
+ SensorListeners sensorListeners = mSensorListeners.get(sensor);
+ if (sensorListeners != null) {
+ writer.println(" Sensor:" + sensor
+ + " num client:" + sensorListeners.getNumberOfClients()
+ + " rate:" + sensorListeners.getRate());
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ writer.println("**driving policy**");
+ mDrivingStatePolicy.dump(writer);
+ writer.println("**day/night policy**");
+ mDayNightModePolicy.dump(writer);
+ }
+}
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
new file mode 100644
index 0000000000..32e47ff230
--- /dev/null
+++ b/service/src/com/android/car/CarService.java
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.car;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import android.app.Service;
+import android.support.car.Car;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+public class CarService extends Service {
+
+ // main thread only
+ private ICarImpl mICarImpl;
+
+ @Override
+ public void onCreate() {
+ Log.i(CarLog.TAG_SERVICE, "Service onCreate");
+ mICarImpl = ICarImpl.getInstance(this);
+ super.onCreate();
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i(CarLog.TAG_SERVICE, "Service onDestroy");
+ ICarImpl.releaseInstance();
+ super.onDestroy();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // keep it alive.
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mICarImpl;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("*dump car service*");
+ }
+}
diff --git a/service/src/com/android/car/CarServiceBase.java b/service/src/com/android/car/CarServiceBase.java
new file mode 100644
index 0000000000..5745b10261
--- /dev/null
+++ b/service/src/com/android/car/CarServiceBase.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.car;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class for all Car specific services.
+ */
+public interface CarServiceBase {
+
+ /**
+ * service is started. All necessary initialization should be done and service should be
+ * functional after this.
+ */
+ void init();
+
+ /** service should stop and all resources should be released. */
+ void release();
+
+ void dump(PrintWriter writer);
+}
diff --git a/service/src/com/android/car/CarServiceUtils.java b/service/src/com/android/car/CarServiceUtils.java
new file mode 100644
index 0000000000..7344af5dc5
--- /dev/null
+++ b/service/src/com/android/car/CarServiceUtils.java
@@ -0,0 +1,22 @@
+/*
+ * 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 com.android.car;
+
+/** Utility class */
+public class CarServiceUtils {
+
+}
diff --git a/service/src/com/android/car/DayNightModePolicy.java b/service/src/com/android/car/DayNightModePolicy.java
new file mode 100644
index 0000000000..2a9c426745
--- /dev/null
+++ b/service/src/com/android/car/DayNightModePolicy.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.car;
+
+import android.content.Context;
+import android.support.car.Car;
+import android.support.car.CarSensorEvent;
+import android.support.car.CarSensorManager;
+
+import com.android.car.hal.SensorHalBase.SensorListener;
+
+import java.io.PrintWriter;
+
+//TODO
+public class DayNightModePolicy extends CarSensorService.LogicalSensorHalBase {
+
+ private final Context mContext;
+ private SensorListener mSensorListener;
+ private boolean mIsReady = false;
+ private boolean mStarted = false;
+
+ public DayNightModePolicy(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public synchronized void init() {
+ mIsReady = true;
+ }
+
+ @Override
+ public synchronized void release() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public synchronized CarSensorEvent getDefaultValue(int sensorType) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public synchronized void registerSensorListener(SensorListener listener) {
+ mSensorListener = listener;
+ if (mIsReady) {
+ mSensorListener.onSensorHalReady(this);
+ }
+ }
+
+ @Override
+ public synchronized void onSensorServiceReady() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public synchronized boolean isReady() {
+ return mIsReady;
+ }
+
+ @Override
+ public synchronized int[] getSupportedSensors() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public synchronized boolean requestSensorStart(int sensorType, int rate) {
+ mStarted = true;
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public synchronized void requestSensorStop(int sensorType) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public synchronized void dump(PrintWriter writer) {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/service/src/com/android/car/DrivingStatePolicy.java b/service/src/com/android/car/DrivingStatePolicy.java
new file mode 100644
index 0000000000..de91318d62
--- /dev/null
+++ b/service/src/com/android/car/DrivingStatePolicy.java
@@ -0,0 +1,207 @@
+/*
+ * 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 com.android.car;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.car.Car;
+import android.support.car.CarSensorEvent;
+import android.support.car.CarSensorManager;
+import android.support.car.ICarSensorEventListener;
+import android.util.Log;
+
+import com.android.car.hal.SensorHalBase.SensorListener;
+
+import java.io.PrintWriter;
+
+/**
+ * Logical sensor implementing driving state policy. This policy sets only two states:
+ * no restriction vs fully restrictive. To enter no restriction state, speed should be zero
+ * while either parking brake is applied or transmission gear is in P.
+ */
+public class DrivingStatePolicy extends CarSensorService.LogicalSensorHalBase {
+
+ private final Context mContext;
+ private CarSensorService mSensorService;
+ private int mDringState = CarSensorEvent.DRIVE_STATUS_FULLY_RESTRICTED;
+ private SensorListener mSensorListener;
+ private boolean mIsReady = false;
+ private boolean mStarted = false;
+
+ private static final int[] SUPPORTED_SENSORS = { CarSensorManager.SENSOR_TYPE_DRIVING_STATUS };
+
+ private final ICarSensorEventListener mICarSensorEventListener =
+ new ICarSensorEventListener.Stub() {
+ @Override
+ public void onSensorChanged(CarSensorEvent event) {
+ handleSensorEvent(event);
+ }
+ };
+
+ public DrivingStatePolicy(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void init() {
+ mIsReady = true;
+ }
+
+ @Override
+ public synchronized void onSensorServiceReady() {
+ mSensorService =
+ (CarSensorService) ICarImpl.getInstance(mContext).getCarService(Car.SENSOR_SERVICE);
+ int sensorList[] = mSensorService.getSupportedSensors();
+ boolean hasSpeed = subscribeIfSupportedLocked(sensorList,
+ CarSensorManager.SENSOR_TYPE_CAR_SPEED, CarSensorManager.SENSOR_RATE_FASTEST);
+ if (!hasSpeed) {
+ Log.w(CarLog.TAG_SENSOR,
+ "No speed sensor from car. Driving state will be always fully restrictive");
+ }
+ boolean hasParkingBrake = subscribeIfSupportedLocked(sensorList,
+ CarSensorManager.SENSOR_TYPE_PARKING_BRAKE, CarSensorManager.SENSOR_RATE_FASTEST);
+ boolean hasGear = subscribeIfSupportedLocked(sensorList, CarSensorManager.SENSOR_TYPE_GEAR,
+ CarSensorManager.SENSOR_RATE_FASTEST);
+ if (!hasParkingBrake && !hasGear) {
+ Log.w(CarLog.TAG_SENSOR,
+ "No brake info from car. Driving state will be always fully restrictive");
+ }
+ }
+
+ @Override
+ public void release() {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public synchronized CarSensorEvent getDefaultValue(int sensorType) {
+ if (sensorType != CarSensorManager.SENSOR_TYPE_DRIVING_STATUS) {
+ Log.w(CarLog.TAG_SENSOR, "getCurrentValue to DrivingStatePolicy with sensorType:" +
+ sensorType);
+ return null;
+ }
+ return createEvent(mDringState);
+ }
+
+ @Override
+ public synchronized void registerSensorListener(SensorListener listener) {
+ mSensorListener = listener;
+ if (mIsReady) {
+ mSensorListener.onSensorHalReady(this);
+ }
+ }
+
+ @Override
+ public synchronized boolean isReady() {
+ return mIsReady;
+ }
+
+ @Override
+ public int[] getSupportedSensors() {
+ return SUPPORTED_SENSORS;
+ }
+
+ @Override
+ public synchronized boolean requestSensorStart(int sensorType, int rate) {
+ mStarted = true;
+ mSensorListener.onSensorData(createEvent(mDringState));
+ return true;
+ }
+
+ @Override
+ public synchronized void requestSensorStop(int sensorType) {
+ mStarted = false;
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ // TODO Auto-generated method stub
+ }
+
+ private boolean subscribeIfSupportedLocked(int sensorList[], int sensorType, int rate) {
+ if (!CarSensorManager.isSensorSupported(sensorList, sensorType)) {
+ Log.i(CarLog.TAG_SENSOR, "Sensor not supported:" + sensorType);
+ return false;
+ }
+ return mSensorService.registerOrUpdateSensorListener(sensorType, rate,
+ CarSensorService.VERSION, mICarSensorEventListener);
+ }
+
+ private synchronized void handleSensorEvent(CarSensorEvent event) {
+ switch (event.sensorType) {
+ case CarSensorManager.SENSOR_TYPE_PARKING_BRAKE:
+ case CarSensorManager.SENSOR_TYPE_GEAR:
+ case CarSensorManager.SENSOR_TYPE_CAR_SPEED:
+ int drivingState = recalcDrivingStateLocked();
+ if (drivingState != mDringState && mSensorListener != null) {
+ mDringState = drivingState;
+ mSensorListener.onSensorData(createEvent(mDringState));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private int recalcDrivingStateLocked() {
+ int drivingState = CarSensorEvent.DRIVE_STATUS_FULLY_RESTRICTED;
+ CarSensorEvent lastParkingBrake = mSensorService.getLatestSensorEvent(
+ CarSensorManager.SENSOR_TYPE_PARKING_BRAKE);
+ CarSensorEvent lastGear = mSensorService.getLatestSensorEvent(
+ CarSensorManager.SENSOR_TYPE_GEAR);
+ CarSensorEvent lastSpeed = mSensorService.getLatestSensorEvent(
+ CarSensorManager.SENSOR_TYPE_CAR_SPEED);
+ if (lastSpeed != null && lastSpeed.floatValues[0] == 0f) { // stopped
+ if (lastParkingBrake == null && isParkingBrakeApplied(lastParkingBrake)) {
+ if (lastGear != null && isGearInParkingOrNeutral(lastGear)) {
+ drivingState = CarSensorEvent.DRIVE_STATUS_UNRESTRICTED;
+ }
+ } else { // parking break not applied or not available
+ if (lastGear != null && isGearInParking(lastGear)) { // gear in P
+ drivingState = CarSensorEvent.DRIVE_STATUS_UNRESTRICTED;
+ }
+ }
+ } // else moving, full restriction
+ return drivingState;
+ }
+
+ private boolean isSpeedZero(CarSensorEvent event) {
+ return event.floatValues[0] == 0f;
+ }
+
+ private boolean isParkingBrakeApplied(CarSensorEvent event) {
+ return event.byteValues[0] == 1;
+ }
+
+ private boolean isGearInParkingOrNeutral(CarSensorEvent event) {
+ byte gear = event.byteValues[0];
+ return (gear == ((byte) CarSensorEvent.GEAR_NEUTRAL & 0xff)) ||
+ (gear == ((byte) CarSensorEvent.GEAR_PARK & 0xff));
+ }
+
+ private boolean isGearInParking(CarSensorEvent event) {
+ byte gear = event.byteValues[0];
+ return gear == ((byte) CarSensorEvent.GEAR_PARK & 0xff);
+ }
+
+ private CarSensorEvent createEvent(int drivingState) {
+ CarSensorEvent event = new CarSensorEvent(CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
+ SystemClock.elapsedRealtimeNanos(), 0, 1);
+ event.byteValues[0] = (byte) (drivingState & 0xff);
+ return event;
+ }
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
new file mode 100644
index 0000000000..081a33afc5
--- /dev/null
+++ b/service/src/com/android/car/ICarImpl.java
@@ -0,0 +1,128 @@
+/*
+ * 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 com.android.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.support.car.Car;
+import android.support.car.CarInfo;
+import android.support.car.CarUiInfo;
+import android.support.car.ICar;
+import android.support.car.ICarConnectionListener;
+import android.support.car.ICarSensor;
+import android.util.Log;
+
+import com.android.car.hal.Hal;
+import com.android.internal.annotations.GuardedBy;
+
+public class ICarImpl extends ICar.Stub {
+ private static final int VERSION = 1;
+
+ @GuardedBy("ICarImpl.class")
+ private static ICarImpl sInstance = null;
+
+ private final Context mContext;
+ private final Hal mHal;
+
+ private final CarSensorService mCarSensorService;
+
+ public synchronized static ICarImpl getInstance(Context serviceContext) {
+ if (sInstance == null) {
+ sInstance = new ICarImpl(serviceContext);
+ sInstance.init();
+ }
+ return sInstance;
+ }
+
+ public synchronized static void releaseInstance() {
+ if (sInstance == null) {
+ return;
+ }
+ sInstance.release();
+ sInstance = null;
+ }
+
+ public ICarImpl(Context serviceContext) {
+ mContext = serviceContext;
+ mHal = Hal.getInstance(serviceContext.getApplicationContext());
+ mCarSensorService = new CarSensorService(serviceContext);
+ }
+
+ private void init() {
+ mCarSensorService.init();
+ }
+
+ private void release() {
+ mCarSensorService.release();
+ Hal.releaseInstance();
+ }
+
+ @Override
+ public int getVersion() {
+ return VERSION;
+ }
+
+ @Override
+ public IBinder getCarService(String serviceName) {
+ switch (serviceName) {
+ case Car.SENSOR_SERVICE:
+ return mCarSensorService;
+ default:
+ Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
+ return null;
+ }
+ }
+
+ @Override
+ public CarInfo getCarInfo() {
+ //TODO
+ return null;
+ }
+
+ @Override
+ public CarUiInfo getCarUiInfo() {
+ //TODO
+ return null;
+ }
+
+ @Override
+ public boolean isConnectedToCar() {
+ return true; // always connected in embedded
+ }
+
+ @Override
+ public int getCarConnectionType() {
+ return Car.CONNECTION_TYPE_EMBEDDED;
+ }
+
+ @Override
+ public void registerCarConnectionListener(int clientVersion, ICarConnectionListener listener) {
+ //TODO
+ }
+
+ @Override
+ public void unregisterCarConnectionListener(ICarConnectionListener listener) {
+ //TODO
+ }
+
+ @Override
+ public boolean startCarActivity(Intent intent) {
+ //TODO
+ return false;
+ }
+}
diff --git a/service/src/com/android/car/hal/Hal.java b/service/src/com/android/car/hal/Hal.java
new file mode 100644
index 0000000000..58e4319eac
--- /dev/null
+++ b/service/src/com/android/car/hal/Hal.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.car.hal;
+
+import android.content.Context;
+
+/**
+ * Abstraction for vehicle HAL.
+ */
+public class Hal {
+
+ private final SensorHal mSensorHal;
+
+ private static Hal sInstance;
+
+ public static synchronized Hal getInstance(Context applicationContext) {
+ if (sInstance == null) {
+ sInstance = new Hal(applicationContext);
+ sInstance.init();
+ }
+ return sInstance;
+ }
+
+ public static synchronized void releaseInstance() {
+ if (sInstance != null) {
+ sInstance.release();
+ sInstance = null;
+ }
+ }
+
+ public Hal(Context applicationContext) {
+ mSensorHal = new SensorHal();
+ }
+
+ public void init() {
+ mSensorHal.init();
+ }
+
+ public void release() {
+ mSensorHal.release();
+ }
+
+ public SensorHal getSensorHal() {
+ return mSensorHal;
+ }
+}
diff --git a/service/src/com/android/car/hal/SensorHal.java b/service/src/com/android/car/hal/SensorHal.java
new file mode 100644
index 0000000000..bc8fd5c1d1
--- /dev/null
+++ b/service/src/com/android/car/hal/SensorHal.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.car.hal;
+
+import java.io.PrintWriter;
+
+/**
+ * TODO
+ * Sensor HAL implementation for physical sensors in car.
+ */
+public class SensorHal extends SensorHalBase {
+
+ private boolean mIsReady = false;
+
+ @Override
+ public synchronized void init() {
+ //TODO
+ mIsReady = true;
+ }
+
+ @Override
+ public synchronized void release() {
+ //TODO
+ }
+
+ @Override
+ public synchronized void registerSensorListener(SensorHalBase.SensorListener listener) {
+ //TODO
+ if (mIsReady) {
+ listener.onSensorHalReady(this);
+ }
+ }
+
+ @Override
+ public synchronized boolean isReady() {
+ //TODO
+ return true;
+ }
+
+ @Override
+ public synchronized int[] getSupportedSensors() {
+ //TODO
+ return null;
+ }
+
+ @Override
+ public synchronized boolean requestSensorStart(int sensorType, int rate) {
+ //TODO
+ return false;
+ }
+
+ @Override
+ public synchronized void requestSensorStop(int sensorType) {
+ //TODO
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/service/src/com/android/car/hal/SensorHalBase.java b/service/src/com/android/car/hal/SensorHalBase.java
new file mode 100644
index 0000000000..12331b2b1c
--- /dev/null
+++ b/service/src/com/android/car/hal/SensorHalBase.java
@@ -0,0 +1,54 @@
+package com.android.car.hal;
+
+import android.support.car.CarSensorEvent;
+
+import java.io.PrintWriter;
+
+
+/**
+ * Common base for all SensorHal implementation.
+ * It is wholly based on subscription and there is no explicit API for polling, but each sensor
+ * should report its initial state immediately after {@link #requestSensorStart(int, int)} call.
+ * It is ok to report sensor data {@link SensorListener#onSensorData(CarSensorEvent)} inside
+ * the {@link #requestSensorStart(int, int)} call.
+ */
+public abstract class SensorHalBase {
+ /**
+ * Listener for monitoring sensor event. Only sensor service will implement this.
+ */
+ public interface SensorListener {
+ /**
+ * Sensor Hal is ready and is fully accessible.
+ * This will be called after {@link SensorHalBase#init()}.
+ */
+ void onSensorHalReady(SensorHalBase hal);
+ /**
+ * Sensor data is available.
+ * @param event
+ */
+ void onSensorData(CarSensorEvent event);
+ }
+
+ /**
+ * Do necessary initialization. After this, {@link #getSupportedSensors()} should work.
+ */
+ public abstract void init();
+
+ public abstract void release();
+
+ public abstract void registerSensorListener(SensorListener listener);
+
+ /**
+ * Sensor HAL should be ready after init call.
+ * @return
+ */
+ public abstract boolean isReady();
+
+ public abstract int[] getSupportedSensors();
+
+ public abstract boolean requestSensorStart(int sensorType, int rate);
+
+ public abstract void requestSensorStop(int sensorType);
+
+ public abstract void dump(PrintWriter writer);
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000000..d1148317ec
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/api_test/Android.mk b/tests/api_test/Android.mk
new file mode 100644
index 0000000000..614bb9304b
--- /dev/null
+++ b/tests/api_test/Android.mk
@@ -0,0 +1,33 @@
+# 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CarApiTest
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES += libcarsupport
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_INSTRUMENTATION_FOR := CarApiTest
+
+include $(BUILD_PACKAGE)
diff --git a/tests/api_test/AndroidManifest.xml b/tests/api_test/AndroidManifest.xml
new file mode 100644
index 0000000000..41c158f3c0
--- /dev/null
+++ b/tests/api_test/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.support.car.apitest" >
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.support.car.apitest"
+ android:label="Tests for Car APIs"/>
+
+ <application android:label="CarApiTest">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/api_test/src/com/android/support/car/apitest/CarActivityTest.java b/tests/api_test/src/com/android/support/car/apitest/CarActivityTest.java
new file mode 100644
index 0000000000..f70f4d9be2
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/CarActivityTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.support.car.Car;
+import android.test.ActivityInstrumentationTestCase2;
+
+public class CarActivityTest extends ActivityInstrumentationTestCase2<TestCarProxyActivity> {
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
+
+ private TestCarProxyActivity mActivity;
+
+ public CarActivityTest(Class<TestCarProxyActivity> activityClass) {
+ super(activityClass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ TestCarActivity.testCleanup();
+ }
+
+ public void testCycle() throws Throwable {
+ TestCarActivity.sCreateTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ TestCarActivity.sStartTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ TestCarActivity.sResumeTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ TestCarActivity.sPauseTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ TestCarActivity.sStopTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ TestCarActivity.sDestroyTestAction = new TestAction<TestCarActivity>() {
+ @Override
+ public void run(TestCarActivity param) {
+ Car car = param.getCarApi();
+ assertTrue(car.isConnected());
+ }
+ };
+ mActivity = getActivity();
+ TestCarActivity.sCreateTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ TestCarActivity.sStartTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ TestCarActivity.sResumeTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ mActivity.finish();
+ TestCarActivity.sPauseTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ TestCarActivity.sStopTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ TestCarActivity.sDestroyTestAction.assertTestRun(DEFAULT_WAIT_TIMEOUT_MS);
+ }
+
+}
diff --git a/tests/api_test/src/com/android/support/car/apitest/CarSensorManagerTest.java b/tests/api_test/src/com/android/support/car/apitest/CarSensorManagerTest.java
new file mode 100644
index 0000000000..ccaef131c2
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/CarSensorManagerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.car.Car;
+import android.support.car.CarSensorEvent;
+import android.support.car.CarSensorManager;
+import android.support.car.ServiceConnectionListener;
+import android.test.AndroidTestCase;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+
+public class CarSensorManagerTest extends AndroidTestCase {
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
+
+ private final Semaphore mConnectionWait = new Semaphore(0);
+
+ private Car mCar;
+ private CarSensorManager mCarSensorManager;
+
+ private final ServiceConnectionListener mConnectionListener = new ServiceConnectionListener() {
+
+ @Override
+ public void onServiceSuspended(int cause) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceConnectionFailed(int cause) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ assertMainThread();
+ mConnectionWait.release();
+ }
+ };
+
+ private void assertMainThread() {
+ assertTrue(Looper.getMainLooper().isCurrentThread());
+ }
+ private void waitForConnection(long timeoutMs) throws InterruptedException {
+ mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCar = new Car(getContext(), mConnectionListener, null);
+ mCar.connect();
+ waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
+ mCarSensorManager =
+ (CarSensorManager) mCar.getCarManager(Car.SENSOR_SERVICE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mCar.disconnect();
+ }
+
+ public void testDrivingPolicy() throws Exception {
+ int[] supportedSensors = mCarSensorManager.getSupportedSensors();
+ assertNotNull(supportedSensors);
+ boolean found = false;
+ for (int sensor: supportedSensors) {
+ if (sensor == CarSensorManager.SENSOR_TYPE_DRIVING_STATUS) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found);
+ assertTrue(mCarSensorManager.isSensorSupported(
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS));
+ assertTrue(CarSensorManager.isSensorSupported(supportedSensors,
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS));
+ CarSensorEvent lastEvent = mCarSensorManager.getLatestSensorEvent(
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS);
+ assertNotNull(lastEvent);
+ }
+}
diff --git a/tests/api_test/src/com/android/support/car/apitest/CarTest.java b/tests/api_test/src/com/android/support/car/apitest/CarTest.java
new file mode 100644
index 0000000000..caf99a4a3b
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/CarTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.car.Car;
+import android.support.car.CarSensorManager;
+import android.support.car.ServiceConnectionListener;
+import android.test.AndroidTestCase;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+
+public class CarTest extends AndroidTestCase {
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
+
+ private final Semaphore mConnectionWait = new Semaphore(0);
+
+ private final ServiceConnectionListener mConnectionListener = new ServiceConnectionListener() {
+
+ @Override
+ public void onServiceSuspended(int cause) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceConnectionFailed(int cause) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ assertMainThread();
+ mConnectionWait.release();
+ }
+ };
+
+ private void assertMainThread() {
+ assertTrue(Looper.getMainLooper().isCurrentThread());
+ }
+ private void waitForConnection(long timeoutMs) throws InterruptedException {
+ mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ public void testCarConnection() throws Exception {
+ Car car = new Car(getContext(), mConnectionListener, null);
+ assertFalse(car.isConnected());
+ assertFalse(car.isConnecting());
+ car.connect();
+ //TODO fix race here
+ assertTrue(car.isConnecting());
+ waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
+ assertTrue(car.isConnected());
+ assertFalse(car.isConnecting());
+ CarSensorManager carSensorManager =
+ (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);
+ assertNotNull(carSensorManager);
+ CarSensorManager carSensorManager2 =
+ (CarSensorManager) car.getCarManager(Car.SENSOR_SERVICE);
+ assertEquals(carSensorManager, carSensorManager2);
+ Object noSuchService = car.getCarManager("No such service");
+ assertNull(noSuchService);
+ // double disconnect should be safe.
+ car.disconnect();
+ car.disconnect();
+ assertFalse(car.isConnected());
+ assertFalse(car.isConnecting());
+ }
+
+ public void testDoubleConnect() throws Exception {
+ Car car = new Car(getContext(), mConnectionListener, null);
+ assertFalse(car.isConnected());
+ assertFalse(car.isConnecting());
+ car.connect();
+ try {
+ car.connect();
+ fail("dobule connect should throw");
+ } catch (IllegalStateException e) {
+ // expected
+ }
+ car.disconnect();
+ }
+}
diff --git a/tests/api_test/src/com/android/support/car/apitest/TestAction.java b/tests/api_test/src/com/android/support/car/apitest/TestAction.java
new file mode 100644
index 0000000000..472b82579d
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/TestAction.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
+
+/**
+ * A generic test action, whose run method will be called with a parameter.
+ */
+public abstract class TestAction<T> {
+ /** for passing additional test specific parameters */
+ public volatile Object arg0;
+ public volatile Object arg1;
+ private static final String TAG = "CAR.TEST";
+ private Throwable mLastThrowable;
+ private Semaphore mWaitSemaphore = new Semaphore(0);
+
+ abstract public void run(T param);
+
+ public void doRun(T param) {
+ Log.i(TAG, "TestAction doRun " + this + " param:" + param);
+ try {
+ run(param);
+ } catch (Throwable t) {
+ Log.i(TAG, "TestAction doRun get exception", t);
+ mLastThrowable = t;
+ }
+ mWaitSemaphore.release();
+ }
+
+ public void assertTestRun(long timeoutMs) throws Throwable {
+ if (!mWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ Assert.fail("timeout");
+ }
+ if (mLastThrowable != null) {
+ throw mLastThrowable;
+ }
+ }
+
+ public void assertNotRun(long waitMs) throws Throwable {
+ if (!mWaitSemaphore.tryAcquire(waitMs, TimeUnit.MILLISECONDS)) {
+ return;
+ }
+ if (mLastThrowable != null) {
+ Log.e(TAG, "Action was run, but failed");
+ throw mLastThrowable;
+ }
+ Assert.fail("Action was run");
+ }
+
+ public boolean waitForTest(long timeoutMs) throws Throwable {
+ if (mWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
+ if (mLastThrowable != null) {
+ throw mLastThrowable;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void resetRunState() {
+ mWaitSemaphore.drainPermits();
+ }
+
+ public static <U> TestAction<U> buildEmptyAction() {
+ return new TestAction<U>() {
+ @Override
+ public void run(U param) {
+ }
+ };
+ }
+}
diff --git a/tests/api_test/src/com/android/support/car/apitest/TestCarActivity.java b/tests/api_test/src/com/android/support/car/apitest/TestCarActivity.java
new file mode 100644
index 0000000000..c6bbf4466d
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/TestCarActivity.java
@@ -0,0 +1,102 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.car.Car;
+import android.support.car.CarNotConnectedException;
+import android.support.car.CarNotSupportedException;
+import android.support.car.app.CarActivity;
+import android.util.Log;
+
+public class TestCarActivity extends CarActivity {
+ private static final String TAG = TestCarActivity.class.getSimpleName();
+
+ public static volatile TestAction<TestCarActivity> sCreateTestAction;
+ public static volatile TestAction<TestCarActivity> sStartTestAction;
+ public static volatile TestAction<TestCarActivity> sResumeTestAction;
+ public static volatile TestAction<TestCarActivity> sPauseTestAction;
+ public static volatile TestAction<TestCarActivity> sStopTestAction;
+ public static volatile TestAction<TestCarActivity> sDestroyTestAction;
+
+ public TestCarActivity(CarActivity.Proxy proxy, Context context, Car carApi) {
+ super(proxy, context, carApi);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) throws CarNotConnectedException {
+ Log.d(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ Log.d(TAG, "TestAction " + sCreateTestAction);
+ doRunTest(sCreateTestAction);
+ }
+
+
+ @Override
+ protected void onStart() throws CarNotConnectedException {
+ Log.d(TAG, "onStart");
+ super.onStart();
+ doRunTest(sStartTestAction);
+ }
+
+ @Override
+ protected void onResume() throws CarNotConnectedException {
+ Log.d(TAG, "onResume");
+ super.onResume();
+ doRunTest(sResumeTestAction);
+
+ }
+
+ @Override
+ protected void onPause() throws CarNotConnectedException {
+ Log.d(TAG, "onPause");
+ super.onPause();
+ doRunTest(sPauseTestAction);
+ }
+
+
+ @Override
+ protected void onStop() throws CarNotConnectedException {
+ Log.d(TAG, "onStop");
+ super.onStop();
+ doRunTest(sStopTestAction);
+ }
+
+ @Override
+ protected void onDestroy() throws CarNotConnectedException {
+ Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ doRunTest(sDestroyTestAction);
+ }
+
+ private void doRunTest(TestAction<TestCarActivity> test) {
+ Log.d(TAG, "doRunTest " + test);
+ if (test != null) {
+ test.doRun(this);
+ }
+ }
+
+ public static void testCleanup() {
+ sCreateTestAction = null;
+ sStartTestAction = null;
+ sResumeTestAction = null;
+ sPauseTestAction = null;
+ sStopTestAction = null;
+ sDestroyTestAction = null;
+ }
+}
diff --git a/tests/api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java b/tests/api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
new file mode 100644
index 0000000000..94abeb1ab4
--- /dev/null
+++ b/tests/api_test/src/com/android/support/car/apitest/TestCarProxyActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.support.car.apitest;
+
+import android.support.car.app.CarProxyActivity;
+
+public class TestCarProxyActivity extends CarProxyActivity {
+
+ public TestCarProxyActivity() {
+ super(TestCarActivity.class);
+ }
+}
diff --git a/tests/car_activity_test_app/Android.mk b/tests/car_activity_test_app/Android.mk
new file mode 100644
index 0000000000..7d0f819cbf
--- /dev/null
+++ b/tests/car_activity_test_app/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CarActivityTestApp
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES += libcarsupport
+
+include $(BUILD_PACKAGE)
diff --git a/tests/car_activity_test_app/AndroidManifest.xml b/tests/car_activity_test_app/AndroidManifest.xml
new file mode 100644
index 0000000000..9367b35ee6
--- /dev/null
+++ b/tests/car_activity_test_app/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.support.car.test.caractivitytest" >
+
+ <application android:label="CarActivityTest"
+ android:icon="@drawable/ic_launcher">
+ <activity android:name=".HelloCarProxyActivity"
+ android:label="HelloCarActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/car_activity_test_app/res/drawable-hdpi/ic_launcher.png b/tests/car_activity_test_app/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..288b66551d
--- /dev/null
+++ b/tests/car_activity_test_app/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/car_activity_test_app/res/drawable-mdpi/ic_launcher.png b/tests/car_activity_test_app/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..6ae570b4db
--- /dev/null
+++ b/tests/car_activity_test_app/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/car_activity_test_app/res/drawable-xhdpi/ic_launcher.png b/tests/car_activity_test_app/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..d4fb7cd9d8
--- /dev/null
+++ b/tests/car_activity_test_app/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/car_activity_test_app/res/drawable-xxhdpi/ic_launcher.png b/tests/car_activity_test_app/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..85a6081587
--- /dev/null
+++ b/tests/car_activity_test_app/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/car_activity_test_app/res/layout/hello_caractivity.xml b/tests/car_activity_test_app/res/layout/hello_caractivity.xml
new file mode 100644
index 0000000000..4f6ddd4f18
--- /dev/null
+++ b/tests/car_activity_test_app/res/layout/hello_caractivity.xml
@@ -0,0 +1,20 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/hello_world" />
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@+id/textView1"
+ android:layout_below="@+id/textView1"
+ android:layout_marginTop="24dp"
+ android:text="@string/button1" />
+</RelativeLayout> \ No newline at end of file
diff --git a/tests/car_activity_test_app/res/values/strings.xml b/tests/car_activity_test_app/res/values/strings.xml
new file mode 100644
index 0000000000..66cf3307da
--- /dev/null
+++ b/tests/car_activity_test_app/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="hello_world">Hello world!</string>
+ <string name="button1">Button</string>
+</resources>
diff --git a/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarActivity.java b/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarActivity.java
new file mode 100644
index 0000000000..2bbf826c81
--- /dev/null
+++ b/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarActivity.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.android.support.car.test.caractivitytest;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.car.Car;
+import android.support.car.CarNotConnectedException;
+import android.support.car.CarNotSupportedException;
+import android.support.car.app.CarActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+public class HelloCarActivity extends CarActivity {
+ private static final String TAG = HelloCarActivity.class.getSimpleName();
+ private TextView mTextView1;
+ private Button mButton1;
+ private int mClickCount = 0;
+
+ public HelloCarActivity(Proxy proxy, Context context, Car carApi) {
+ super(proxy, context, carApi);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ throws CarNotConnectedException {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.hello_caractivity);
+ mTextView1 = (TextView) findViewById(R.id.textView1);
+ mButton1 = (Button) findViewById(R.id.button1);
+ mButton1.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ mClickCount++;
+ mTextView1.setText("You clicked :" + mClickCount);
+ }
+ });
+ Log.i(TAG, "onCreate");
+ }
+
+ @Override
+ protected void onStart() throws CarNotConnectedException {
+ super.onStart();
+ Log.i(TAG, "onStart");
+ }
+
+ @Override
+ protected void onRestart() throws CarNotConnectedException {
+ super.onRestart();
+ Log.i(TAG, "onRestart");
+ }
+
+ @Override
+ protected void onResume() throws CarNotConnectedException {
+ super.onResume();
+ Log.i(TAG, "onResume");
+ }
+
+ @Override
+ protected void onPause() throws CarNotConnectedException {
+ super.onPause();
+ Log.i(TAG, "onPause");
+ }
+
+ @Override
+ protected void onStop() throws CarNotConnectedException {
+ super.onStop();
+ Log.i(TAG, "onStop");
+ }
+
+ @Override
+ protected void onDestroy() throws CarNotConnectedException {
+ super.onDestroy();
+ Log.i(TAG, "onDestroy");
+ }
+
+}
diff --git a/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarProxyActivity.java b/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarProxyActivity.java
new file mode 100644
index 0000000000..ce5ed1a863
--- /dev/null
+++ b/tests/car_activity_test_app/src/com/android/support/car/test/caractivitytest/HelloCarProxyActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.support.car.test.caractivitytest;
+
+import android.support.car.app.CarProxyActivity;
+
+public class HelloCarProxyActivity extends CarProxyActivity {
+
+ public HelloCarProxyActivity() {
+ super(HelloCarActivity.class);
+ }
+}