diff options
author | keunyoung <keunyoung@google.com> | 2015-07-10 12:21:47 -0700 |
---|---|---|
committer | Rakesh Iyer <rni@google.com> | 2015-11-17 15:04:55 -0800 |
commit | ca515079e9fc0c35b1498830f67378e9ccf949e5 (patch) | |
tree | 39423bf6966344bfeaf4521f265b48d59909f1d5 | |
parent | a20c9699d0d55f6ffa704ce577e5cf489e9307c9 (diff) | |
download | Car-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)
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 Binary files differnew file mode 100644 index 0000000000..288b66551d --- /dev/null +++ b/tests/car_activity_test_app/res/drawable-hdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000000..6ae570b4db --- /dev/null +++ b/tests/car_activity_test_app/res/drawable-mdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000000..d4fb7cd9d8 --- /dev/null +++ b/tests/car_activity_test_app/res/drawable-xhdpi/ic_launcher.png 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 Binary files differnew file mode 100644 index 0000000000..85a6081587 --- /dev/null +++ b/tests/car_activity_test_app/res/drawable-xxhdpi/ic_launcher.png 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); + } +} |