Merge nyc-car-dev
0903541d Update lens-header-height to correspond to new A/R specs.
e315d2ea Instrument android.car when EMMA_INSTRUMENT_FRAMEWORK is true.
cd68cc79 cleanup util classes
15ba9074 Update colors in car-support-ui.
19c8ab20 Docs: Editing SDK java docs
a9c9a7b8 Allow overriding the drawer item cap.
b94c5fde Merge "Docs: Editing SDK java docs" into nyc-car-dev
2f581594 Add tests for CarApiUtil
f4f07bf0 use main looper when null Handler is passed to Car
81ca2491 Merge "Add tests for CarApiUtil" into nyc-car-dev
aa5558ff Merge "use main looper when null Handler is passed to Car" into nyc-car-dev
edb80c84 Fix CarAppFocusManagerTest + improved coverage
52d7da59 Move CarVendorExtensionManagerTest to carservice
b88532d8 Merge changes If292161e,I0ed86319 into nyc-car-dev
51101d3c Add styles for Key1 and Key2.
8d90e50f Add tests for CarProjectionManager.
77ac6cf7 more test for Car
9cea1148 use only android.Car in car service test
53fe859e add CarInfoManagerTest and drop IAE from CarInfoManager
eff0a365 Merge "more test for Car" into nyc-car-dev
ca848cf1 Merge "add CarInfoManagerTest and drop IAE from CarInfoManager" into nyc-car-dev
5d873a0a Merge "use only android.Car in car service test" into nyc-car-dev
1d55dcf2 Fix ConcurrentModificationException in car support sensors proxy.
a8ecc9bc Update tests for VehicleZoneUtil
6e796904 Merge "Update tests for VehicleZoneUtil" into nyc-car-dev
8dd47a8e Consolidate naming of dimens in car-support-lib.
b0324b44 Add on-property-set listener to VNS
1b1247b5 Change logging for car packages.
4505f41a Merge "Add on-property-set listener to VNS" into nyc-car-dev
fb43d3da Merge "Change logging for car packages." into nyc-car-dev
4fbde4fb removing extendableParcelable and related changes
d72b5350 Many API council fixes.
67456359 CarInfoManager, put basic info into one Bundle
4cf69116 add final to Car*Managers
d15d8877 Change CarAppFocusManager to be asynchronous
09187673 Fix VehicleZoneUtilTest
98960815 fix race in app blocking during bootup
20dd8939 Merge "Change CarAppFocusManager to be asynchronous" into nyc-car-dev
cb7e36bd Merge "Fix VehicleZoneUtilTest" into nyc-car-dev
407a8df5 Merge "fix race in app blocking during bootup" into nyc-car-dev
3388e784 instument cluster api review
14134940 car api review
07ddbbd7 add specific access methods to CarInfoManager
Test: build, car tests, cts
Change-Id: I791c90c6a44bbf78a4535c747776a0c9fd9b5178
diff --git a/car-default-input-service/Android.mk b/car-default-input-service/Android.mk
new file mode 100644
index 0000000..59ef91c
--- /dev/null
+++ b/car-default-input-service/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2016 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 := android.car.input.service
+
+# 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_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
diff --git a/car-default-input-service/AndroidManifest.xml b/car-default-input-service/AndroidManifest.xml
new file mode 100644
index 0000000..0e15e7d
--- /dev/null
+++ b/car-default-input-service/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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"
+ package="android.car.input.service"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
+ android:directBootAware="true">
+ <service android:name=".DefaultInputService"
+ android:permission="android.car.permission.BIND_CAR_INPUT_SERVICE"
+ android:exported="false"
+ />
+ </application>
+</manifest>
diff --git a/car-default-input-service/proguard.flags b/car-default-input-service/proguard.flags
new file mode 100644
index 0000000..565df26
--- /dev/null
+++ b/car-default-input-service/proguard.flags
@@ -0,0 +1,2 @@
+-verbose
+-keep @com.android.internal.annotations.VisibleForTesting class *
diff --git a/car-default-input-service/res/drawable-hdpi/ic_launcher.png b/car-default-input-service/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/car-default-input-service/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/car-default-input-service/res/drawable-ldpi/ic_launcher.png b/car-default-input-service/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/car-default-input-service/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/car-default-input-service/res/drawable-mdpi/ic_launcher.png b/car-default-input-service/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/car-default-input-service/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/car-default-input-service/res/drawable-xhdpi/ic_launcher.png b/car-default-input-service/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/car-default-input-service/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/car-default-input-service/res/values/strings.xml b/car-default-input-service/res/values/strings.xml
new file mode 100644
index 0000000..c4dbd46
--- /dev/null
+++ b/car-default-input-service/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<resources>
+ <string name="app_name">Car Default Input Service</string>
+</resources>
diff --git a/car-default-input-service/src/android/car/input/service/DefaultInputService.java b/car-default-input-service/src/android/car/input/service/DefaultInputService.java
new file mode 100644
index 0000000..2502525
--- /dev/null
+++ b/car-default-input-service/src/android/car/input/service/DefaultInputService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 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.car.input.service;
+
+import android.car.input.CarInputHandlingService;
+import android.car.input.CarInputHandlingService.InputFilter;
+import android.util.Log;
+import android.view.KeyEvent;
+
+/**
+ * Default implementation of {@link CarInputHandlingService}.
+ *
+ * This implementation does nothing, just serves as showcase how input service should be
+ * implemented.
+ */
+public class DefaultInputService extends CarInputHandlingService {
+ private static final String TAG = DefaultInputService.class.getSimpleName();
+
+ public DefaultInputService() {
+ super(new InputFilter[0]);
+ }
+
+ @Override
+ protected void onKeyEvent(KeyEvent event, int targetDisplay) {
+ // Implement your handling here
+ Log.i(TAG, "onKeyEvent(" + event + ", " + targetDisplay + ")");
+ }
+}
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 2415fd4..d1e4dd7 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -379,7 +379,7 @@
package android.car.hardware {
- public class CarPropertyConfig implements android.os.Parcelable {
+ public class CarPropertyConfig<T> implements android.os.Parcelable {
method public int describeContents();
method public int getAreaCount();
method public int[] getAreaIds();
@@ -393,13 +393,13 @@
method public java.lang.Class<T> getPropertyType();
method public boolean hasArea(int);
method public boolean isGlobalProperty();
- method public static android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int, int);
- method public static android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int);
+ method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int, int);
+ method public static <T> android.car.hardware.CarPropertyConfig.Builder<T> newBuilder(java.lang.Class<T>, int, int);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyConfig> CREATOR;
}
- public static class CarPropertyConfig.AreaConfig implements android.os.Parcelable {
+ public static class CarPropertyConfig.AreaConfig<T> implements android.os.Parcelable {
method public int describeContents();
method public T getMaxValue();
method public T getMinValue();
@@ -407,14 +407,14 @@
field public static final android.os.Parcelable.Creator<android.car.hardware.CarPropertyConfig.AreaConfig<java.lang.Object>> CREATOR;
}
- public static class CarPropertyConfig.Builder {
+ public static class CarPropertyConfig.Builder<T> {
method public android.car.hardware.CarPropertyConfig.Builder<T> addArea(int);
method public android.car.hardware.CarPropertyConfig.Builder<T> addAreaConfig(int, T, T);
method public android.car.hardware.CarPropertyConfig.Builder<T> addAreas(int[]);
method public android.car.hardware.CarPropertyConfig<T> build();
}
- public class CarPropertyValue implements android.os.Parcelable {
+ public class CarPropertyValue<T> implements android.os.Parcelable {
ctor public CarPropertyValue(int, T);
ctor public CarPropertyValue(int, int, T);
ctor public CarPropertyValue(android.os.Parcel);
@@ -497,12 +497,12 @@
}
public final class CarVendorExtensionManager {
- method public E getGlobalProperty(java.lang.Class<E>, int) throws android.car.CarNotConnectedException;
+ method public <E> E getGlobalProperty(java.lang.Class<E>, int) throws android.car.CarNotConnectedException;
method public java.util.List<android.car.hardware.CarPropertyConfig> getProperties() throws android.car.CarNotConnectedException;
- method public E getProperty(java.lang.Class<E>, int, int) throws android.car.CarNotConnectedException;
+ method public <E> E getProperty(java.lang.Class<E>, int, int) throws android.car.CarNotConnectedException;
method public void registerListener(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionListener) throws android.car.CarNotConnectedException;
- method public void setGlobalProperty(java.lang.Class<E>, int, E) throws android.car.CarNotConnectedException;
- method public void setProperty(java.lang.Class<E>, int, int, E) throws android.car.CarNotConnectedException;
+ method public <E> void setGlobalProperty(java.lang.Class<E>, int, E) throws android.car.CarNotConnectedException;
+ method public <E> void setProperty(java.lang.Class<E>, int, int, E) throws android.car.CarNotConnectedException;
method public void unregisterListener(android.car.hardware.CarVendorExtensionManager.CarVendorExtensionListener) throws android.car.CarNotConnectedException;
}
@@ -713,6 +713,27 @@
}
+package android.car.input {
+
+ public abstract class CarInputHandlingService extends android.app.Service {
+ ctor protected CarInputHandlingService(android.car.input.CarInputHandlingService.InputFilter[]);
+ method public android.os.IBinder onBind(android.content.Intent);
+ method protected abstract void onKeyEvent(android.view.KeyEvent, int);
+ field public static final int INPUT_CALLBACK_BINDER_CODE = 1; // 0x1
+ field public static final java.lang.String INPUT_CALLBACK_BINDER_KEY = "callback_binder";
+ }
+
+ public static class CarInputHandlingService.InputFilter implements android.os.Parcelable {
+ ctor public CarInputHandlingService.InputFilter(int, int);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public final int mKeyCode;
+ field public final int mTargetDisplay;
+ }
+
+}
+
package android.car.media {
public final class CarAudioManager {
diff --git a/car-lib/src/android/car/CarLibLog.java b/car-lib/src/android/car/CarLibLog.java
index 0a78216..20f2605 100644
--- a/car-lib/src/android/car/CarLibLog.java
+++ b/car-lib/src/android/car/CarLibLog.java
@@ -19,7 +19,8 @@
/** @hide */
public class CarLibLog {
public static final String TAG_CAR = "CAR.L";
- public static final String TAG_SENSOR = TAG_CAR + ".SENSOR";
- public static final String TAG_NAV = TAG_CAR + ".NAV";
public static final String TAG_CLUSTER = TAG_CAR + ".CLUSTER";
+ public static final String TAG_INPUT = TAG_CAR + ".INPUT";
+ public static final String TAG_NAV = TAG_CAR + ".NAV";
+ public static final String TAG_SENSOR = TAG_CAR + ".SENSOR";
}
diff --git a/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl b/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl
new file mode 100644
index 0000000..3e77357
--- /dev/null
+++ b/car-lib/src/android/car/IUsbAoapSupportCheckService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.car;
+
+import android.hardware.usb.UsbDevice;
+
+/**
+ * Binder interface for service to check USB AOAP mode support.
+ *
+ * @hide
+ */
+interface IUsbAoapSupportCheckService {
+ boolean isDeviceSupported(in UsbDevice device) = 0;
+}
diff --git a/car-lib/src/android/car/input/CarInputHandlingService.java b/car-lib/src/android/car/input/CarInputHandlingService.java
new file mode 100644
index 0000000..ea952dd
--- /dev/null
+++ b/car-lib/src/android/car/input/CarInputHandlingService.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 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.car.input;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.car.CarLibLog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * A service that is used for handling of input events.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@code android.car.permission.BIND_CAR_INPUT_SERVICE} permission
+ * <pre>
+ * <service android:name=".MyCarInputService"
+ * android:permission="android.car.permission.BIND_CAR_INPUT_SERVICE">
+ * </service></pre>
+ * <p>Also, you will need to register this service in the following configuration file:
+ * {@code packages/services/Car/service/res/values/config.xml}
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class CarInputHandlingService extends Service {
+ private static final String TAG = CarLibLog.TAG_INPUT;
+ private static final boolean DBG = false;
+
+ public static final String INPUT_CALLBACK_BINDER_KEY = "callback_binder";
+ public static final int INPUT_CALLBACK_BINDER_CODE = IBinder.FIRST_CALL_TRANSACTION;
+
+ private final InputFilter[] mHandledKeys;
+
+ private InputBinder mInputBinder;
+
+ protected CarInputHandlingService(InputFilter[] handledKeys) {
+ if (handledKeys == null) {
+ throw new IllegalArgumentException("handledKeys is null");
+ }
+
+ mHandledKeys = new InputFilter[handledKeys.length];
+ System.arraycopy(handledKeys, 0, mHandledKeys, 0, handledKeys.length);
+ }
+
+ @Override
+ @CallSuper
+ public IBinder onBind(Intent intent) {
+ if (DBG) {
+ Log.d(TAG, "onBind, intent: " + intent);
+ }
+
+ doCallbackIfPossible(intent.getExtras());
+
+ if (mInputBinder == null) {
+ mInputBinder = new InputBinder();
+ }
+
+ return mInputBinder;
+ }
+
+ private void doCallbackIfPossible(Bundle extras) {
+ if (extras == null) {
+ Log.i(TAG, "doCallbackIfPossible: extras are null");
+ return;
+ }
+ IBinder callbackBinder = extras.getBinder(INPUT_CALLBACK_BINDER_KEY);
+ if (callbackBinder == null) {
+ Log.i(TAG, "doCallbackIfPossible: callback IBinder is null");
+ return;
+ }
+ Parcel dataIn = Parcel.obtain();
+ dataIn.writeTypedArray(mHandledKeys, 0);
+ try {
+ callbackBinder.transact(INPUT_CALLBACK_BINDER_CODE, dataIn, null, IBinder.FLAG_ONEWAY);
+ } catch (RemoteException e) {
+ Log.e(TAG, "doCallbackIfPossible: callback failed", e);
+ }
+ }
+
+ /**
+ * Called when key event has been received.
+ */
+ @MainThread
+ protected abstract void onKeyEvent(KeyEvent keyEvent, int targetDisplay);
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ writer.println("**" + getClass().getSimpleName() + "**");
+ writer.println("input binder: " + mInputBinder);
+ }
+
+ private class InputBinder extends ICarInputListener.Stub {
+ private final EventHandler mEventHandler;
+
+ InputBinder() {
+ mEventHandler = new EventHandler(CarInputHandlingService.this);
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent keyEvent, int targetDisplay) throws RemoteException {
+ mEventHandler.doKeyEvent(keyEvent, targetDisplay);
+ }
+ }
+
+ private static class EventHandler extends Handler {
+ private static final int KEY_EVENT = 0;
+ private final WeakReference<CarInputHandlingService> mRefService;
+
+ EventHandler(CarInputHandlingService service) {
+ mRefService = new WeakReference<>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ CarInputHandlingService service = mRefService.get();
+ if (service == null) {
+ return;
+ }
+
+ if (msg.what == KEY_EVENT) {
+ service.onKeyEvent((KeyEvent) msg.obj, msg.arg1);
+ } else {
+ throw new IllegalArgumentException("Unexpected message: " + msg);
+ }
+ }
+
+ void doKeyEvent(KeyEvent event, int targetDisplay) {
+ sendMessage(obtainMessage(KEY_EVENT, targetDisplay, 0, event));
+ }
+ }
+
+ /**
+ * Filter for input events that are handled by custom service.
+ */
+ public static class InputFilter implements Parcelable {
+ public final int mKeyCode;
+ public final int mTargetDisplay;
+
+ public InputFilter(int keyCode, int targetDisplay) {
+ mKeyCode = keyCode;
+ mTargetDisplay = targetDisplay;
+ }
+
+ // Parcelling part
+ InputFilter(Parcel in) {
+ mKeyCode = in.readInt();
+ mTargetDisplay = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mKeyCode);
+ dest.writeInt(mTargetDisplay);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public InputFilter createFromParcel(Parcel in) {
+ return new InputFilter(in);
+ }
+
+ public InputFilter[] newArray(int size) {
+ return new InputFilter[size];
+ }
+ };
+ }
+}
diff --git a/car-lib/src/android/car/input/ICarInputListener.aidl b/car-lib/src/android/car/input/ICarInputListener.aidl
new file mode 100644
index 0000000..d198028
--- /dev/null
+++ b/car-lib/src/android/car/input/ICarInputListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.car.input;
+
+import android.view.KeyEvent;
+
+/**
+ * Binder API for Input Service.
+ *
+ * @hide
+ */
+oneway interface ICarInputListener {
+ /** Called when key event has been received. */
+ void onKeyEvent(in KeyEvent keyEvent, int targetDisplay) = 1;
+}
diff --git a/car-support-lib/Android.mk b/car-support-lib/Android.mk
index ba534d1..beb1e28 100644
--- a/car-support-lib/Android.mk
+++ b/car-support-lib/Android.mk
@@ -33,7 +33,7 @@
LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
LOCAL_RESOURCE_DIR += frameworks/support/v7/recyclerview/res
LOCAL_RESOURCE_DIR += frameworks/support/v7/cardview/res
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := 24
LOCAL_MANIFEST_FILE := AndroidManifest.xml
@@ -59,7 +59,7 @@
# Build the resources.
include $(CLEAR_VARS)
LOCAL_MODULE := android.support.car-res
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := 24
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res
@@ -83,7 +83,7 @@
LOCAL_MODULE := android.support.car
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := 24
LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
diff --git a/car-support-lib/api/current.txt b/car-support-lib/api/current.txt
index 82a8f0a..821eefb 100644
--- a/car-support-lib/api/current.txt
+++ b/car-support-lib/api/current.txt
@@ -6,7 +6,7 @@
method public static android.support.car.Car createCar(android.content.Context, android.support.car.ServiceConnectionCallback);
method public void disconnect();
method public java.lang.Object getCarManager(java.lang.String) throws android.support.car.CarNotConnectedException;
- method public T getCarManager(java.lang.Class<T>) throws android.support.car.CarNotConnectedException;
+ method public <T> T getCarManager(java.lang.Class<T>) throws android.support.car.CarNotConnectedException;
method public boolean isConnected();
method public boolean isConnectedToCar();
method public boolean isConnecting();
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 77c07b9..4cefa05 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -49,7 +49,6 @@
$(call inherit-product-if-exists, external/google-fonts/coming-soon/fonts.mk)
$(call inherit-product-if-exists, external/google-fonts/cutive-mono/fonts.mk)
$(call inherit-product-if-exists, external/noto-fonts/fonts.mk)
-$(call inherit-product-if-exists, external/naver-fonts/fonts.mk)
$(call inherit-product-if-exists, external/roboto-fonts/fonts.mk)
$(call inherit-product-if-exists, external/hyphenation-patterns/patterns.mk)
$(call inherit-product-if-exists, frameworks/base/data/keyboards/keyboards.mk)
@@ -79,10 +78,12 @@
# Automotive specific packages
PRODUCT_PACKAGES += \
vehicle_network_service \
+ vehicle_monitor_service \
CarService \
CarUiProvider \
android.car \
libvehiclenetwork-native \
+ libvehiclemonitor-native \
vns_policy.xml
# Boot animation
diff --git a/car_product/init/init.car.rc b/car_product/init/init.car.rc
index 220ca48..9812d46 100644
--- a/car_product/init/init.car.rc
+++ b/car_product/init/init.car.rc
@@ -1,3 +1,8 @@
+service vms /system/bin/vehicle_monitor_service
+ class core
+ user root
+ group root
+ critical
service vns /system/bin/vehicle_network_service
class core
@@ -7,3 +12,4 @@
on boot
start vns
+ start vms
diff --git a/car_product/sepolicy/file_contexts b/car_product/sepolicy/file_contexts
index e95a012..182c923 100644
--- a/car_product/sepolicy/file_contexts
+++ b/car_product/sepolicy/file_contexts
@@ -3,7 +3,6 @@
# System files
#
/system/bin/vehicle_network_service u:object_r:vns_exec:s0
+/system/bin/vehicle_monitor_service u:object_r:vms_exec:s0
###################################
-
-
diff --git a/car_product/sepolicy/vehicle_monitor_service.te b/car_product/sepolicy/vehicle_monitor_service.te
new file mode 100644
index 0000000..16c6f81
--- /dev/null
+++ b/car_product/sepolicy/vehicle_monitor_service.te
@@ -0,0 +1,4 @@
+type vehicle_monitor_service_exec, exec_type, file_type;
+type vehicle_monitor_service, domain;
+
+init_daemon_domain(vehicle_monitor_service)
diff --git a/car_product/sepolicy/vms.te b/car_product/sepolicy/vms.te
new file mode 100644
index 0000000..fc77b53
--- /dev/null
+++ b/car_product/sepolicy/vms.te
@@ -0,0 +1,11 @@
+# Vehicle monitor service
+type vms, domain;
+type vms_exec, exec_type, file_type;
+
+allow vms system_app:binder { call };
+allow vms car_service:service_manager { add };
+allow vms priv_app:binder { call };
+
+init_daemon_domain(vms)
+
+binder_use(vms);
\ No newline at end of file
diff --git a/libvehiclemonitor/Android.mk b/libvehiclemonitor/Android.mk
new file mode 100644
index 0000000..23bc789
--- /dev/null
+++ b/libvehiclemonitor/Android.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2016 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/libvehiclemonitor/include/HandlerThread.h b/libvehiclemonitor/include/HandlerThread.h
new file mode 100644
index 0000000..03f523e
--- /dev/null
+++ b/libvehiclemonitor/include/HandlerThread.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef LIBVEHICLEMONITOR_HANDLER_THREAD_H_
+#define LIBVEHICLEMONITOR_HANDLER_THREAD_H_
+
+#include <utils/Looper.h>
+#include <utils/threads.h>
+
+namespace android {
+
+/**
+ * Native HandlerThread implementation looking similar to Java version.
+ */
+class HandlerThread : public Thread {
+public:
+ HandlerThread();
+ virtual ~HandlerThread();
+
+ sp<Looper> getLooper();
+ status_t start(const char* name = 0, int32_t priority = PRIORITY_DEFAULT, size_t stack = 0);
+ void quit();
+
+private:
+ bool threadLoop();
+
+private:
+ sp<Looper> mLooper;
+ mutable Mutex mLock;
+ bool mShouldQuit;
+ Condition mLooperWait;
+};
+
+};
+
+#endif /* LIBVEHICLEMONITOR_HANDLER_THREAD_H_ */
diff --git a/libvehiclemonitor/include/IVehicleMonitor.h b/libvehiclemonitor/include/IVehicleMonitor.h
new file mode 100644
index 0000000..4aa2e14
--- /dev/null
+++ b/libvehiclemonitor/include/IVehicleMonitor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_IVEHICLE_MONITOR_H
+#define ANDROID_IVEHICLE_MONITOR_H
+
+#include <binder/Parcel.h>
+
+#include <IVehicleMonitorListener.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+/**
+ * Application priorities used in vehicle monitoring.
+ */
+enum vehicle_app_priority {
+ VEHICLE_APP_PRIORITY_NONE = 0,
+ VEHICLE_APP_PRIORITY_FOREGROUND = 1,
+};
+
+// ----------------------------------------------------------------------------
+
+class IVehicleMonitor : public IInterface {
+public:
+ static const char SERVICE_NAME[];
+ DECLARE_META_INTERFACE(VehicleMonitor);
+
+ virtual status_t setAppPriority(
+ uint32_t pid, uint32_t uid, vehicle_app_priority priority) = 0;
+ virtual status_t setMonitorListener(
+ const sp<IVehicleMonitorListener> &listener) = 0;
+};
+// ----------------------------------------------------------------------------
+
+class BnVehicleMonitor : public BnInterface<IVehicleMonitor> {
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif /* ANDROID_IVEHICLE_MONITOR_H */
diff --git a/libvehiclemonitor/include/IVehicleMonitorListener.h b/libvehiclemonitor/include/IVehicleMonitorListener.h
new file mode 100644
index 0000000..9c9e1f5
--- /dev/null
+++ b/libvehiclemonitor/include/IVehicleMonitorListener.h
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_IVEHICLE_MONITOR_LISTENER_H
+#define ANDROID_IVEHICLE_MONITOR_LISTENER_H
+
+#include <binder/Parcel.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+class IVehicleMonitorListener : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(VehicleMonitorListener);
+
+ /**
+ * Notifies when app misbehaved. Client (Bn implementor) should
+ * hold sp to keep the data received outside this call.
+ */
+ virtual void onAppViolation(
+ int32_t pid, int32_t uid, int32_t action, int32_t violation) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnVehicleMonitorListener : public BnInterface<IVehicleMonitorListener>
+{
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif /* ANDROID_IVEHICLE_MONITOR_LISTENER_H */
diff --git a/libvehiclemonitor/include/VehicleMonitor.h b/libvehiclemonitor/include/VehicleMonitor.h
new file mode 100644
index 0000000..12510ec
--- /dev/null
+++ b/libvehiclemonitor/include/VehicleMonitor.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef ANDROID_VEHICLE_MONITOR_H
+#define ANDROID_VEHICLE_MONITOR_H
+
+#include <binder/IInterface.h>
+
+#include <utils/Mutex.h>
+
+#include "IVehicleMonitor.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+/**
+ * Vehicle monitor API for low level components like HALs to access
+ * monitoring service.
+ * This is reference counted. So use with sp<>.
+ */
+class VehicleMonitor : public IBinder::DeathRecipient {
+public:
+ /**
+ * Factory method for VehicleMonitor. Client should use this method
+ * to create a new instance.
+ */
+ static sp<VehicleMonitor> createVehicleMonitor();
+
+ virtual ~VehicleMonitor();
+
+ status_t setAppPriority(
+ uint32_t pid, uint32_t uid, vehicle_app_priority priority);
+
+ //IBinder::DeathRecipient, not for client
+ void binderDied(const wp<IBinder>& who);
+
+private:
+ VehicleMonitor(sp<IVehicleMonitor>& vehicleMonitor);
+ // RefBase
+ virtual void onFirstRef();
+ sp<IVehicleMonitor> getService();
+
+private:
+ sp<IVehicleMonitor> mService;
+ Mutex mLock;
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif /* ANDROID_VEHICLE_MONITOR_H */
diff --git a/libvehiclemonitor/java/Android.mk b/libvehiclemonitor/java/Android.mk
new file mode 100644
index 0000000..1ced365
--- /dev/null
+++ b/libvehiclemonitor/java/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2016 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 := libvehiclemonitor-java
+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/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl
new file mode 100644
index 0000000..0c495ec
--- /dev/null
+++ b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitor.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 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.vehiclemonitor;
+
+import com.android.car.vehiclemonitor.IVehicleMonitorListener;
+
+/**
+ * Binder API to access vehicle monitor service.
+ * @hide
+ */
+oneway interface IVehicleMonitor {
+ void setAppPriority(int pid, int uid, int priority) = 0;
+ void setMonitorListener(IVehicleMonitorListener listener) = 1;
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
new file mode 100644
index 0000000..ca0ac83
--- /dev/null
+++ b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/IVehicleMonitorListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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.vehiclemonitor;
+
+/**
+ * Listener for vehicle monitor service.
+ * @hide
+ */
+oneway interface IVehicleMonitorListener {
+ void onAppViolation(int pid, int uid, int action, int violation) = 0;
+}
diff --git a/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java
new file mode 100644
index 0000000..f5a9363
--- /dev/null
+++ b/libvehiclemonitor/java/src/com/android/car/vehiclemonitor/VehicleMonitor.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2016 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.vehiclemonitor;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * System API to access Vehicle monitor. This is only for system services and applications should
+ * not use this. All APIs will fail with security error if normal app tries this.
+ */
+public class VehicleMonitor {
+ private static final String TAG = VehicleMonitor.class.getSimpleName();
+
+ private final IVehicleMonitor mService;
+ private final VehicleMonitorListener mListener;
+ private final IVehicleMonitorListenerImpl mVehicleMonitorListener;
+ private final EventHandler mEventHandler;
+
+ private static final int VMS_CONNECT_MAX_RETRY = 10;
+ private static final long VMS_RETRY_WAIT_TIME_MS = 1000;
+
+ /**
+ * Application priorities used in vehicle monitoring.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ApplicationPriority.NONE,
+ ApplicationPriority.FOREGROUND
+ })
+ public @interface ApplicationPriority {
+ int NONE = 0;
+ int FOREGROUND = 1;
+ }
+
+ /**
+ * Listener for VMS events.
+ */
+ public interface VehicleMonitorListener {
+ void onAppViolation(int pid, int uid, int action, int violation);
+ }
+
+ /**
+ * Factory method to create VehicleMonitor
+ */
+ public static VehicleMonitor createVehicleMonitor(
+ VehicleMonitorListener listener, Looper looper) {
+ int retryCount = 0;
+ IVehicleMonitor service = null;
+ while (true) {
+ service = IVehicleMonitor.Stub.asInterface(
+ ServiceManager.getService(IVehicleMonitor.class.getCanonicalName()));
+ if (service != null) {
+ break;
+ }
+ retryCount++;
+ if (retryCount > VMS_CONNECT_MAX_RETRY) {
+ break;
+ }
+ try {
+ Thread.sleep(VMS_RETRY_WAIT_TIME_MS);
+ } catch (InterruptedException e) {
+ //ignore
+ }
+ }
+ if (service == null) {
+ throw new RuntimeException("Vehicle monitor service not available:"
+ + IVehicleMonitor.class.getCanonicalName());
+ }
+ return new VehicleMonitor(service, listener, looper);
+ }
+
+ private VehicleMonitor(
+ IVehicleMonitor service, VehicleMonitorListener listener, Looper looper) {
+ mService = service;
+ mListener = listener;
+ mEventHandler = new EventHandler(looper);
+ mVehicleMonitorListener = new IVehicleMonitorListenerImpl(this);
+ try {
+ mService.setMonitorListener(mVehicleMonitorListener);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Vehicle monitor service not working ", e);
+ }
+ }
+
+ /**
+ * Set application priority.
+ * <p>
+ * This will lead into writing application priority into vehicle monitor.
+ */
+ public void setAppPriority(int pid, int uid, @ApplicationPriority int priority)
+ throws ServiceSpecificException {
+ try {
+ mService.setAppPriority(pid, uid, priority);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Vehicle monitor service not working ", e);
+ }
+ }
+
+ private void handleVehicleMonitorAppViolation(AppViolation appViolation) {
+ mListener.onAppViolation(appViolation.mPid, appViolation.mUid, appViolation.mAction,
+ appViolation.mViolation);
+ }
+
+ private class EventHandler extends Handler {
+
+ private static final int MSG_APP_VIOLATION = 0;
+
+ private EventHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void notifyAppViolation(int pid, int uid, int action, int violation) {
+ AppViolation appViolation = new AppViolation(pid, uid, action, violation);
+ Message msg = obtainMessage(MSG_APP_VIOLATION, appViolation);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_APP_VIOLATION:
+ AppViolation appViolation = (AppViolation) msg.obj;
+ handleVehicleMonitorAppViolation(appViolation);
+ break;
+ }
+ }
+ }
+
+ private static class IVehicleMonitorListenerImpl extends IVehicleMonitorListener.Stub {
+
+ private final WeakReference<VehicleMonitor> mVehicleMonitor;
+
+ private IVehicleMonitorListenerImpl(VehicleMonitor vehicleNewotk) {
+ mVehicleMonitor = new WeakReference<>(vehicleNewotk);
+ }
+
+ @Override
+ public void onAppViolation(int pid, int uid, int action, int violation) {
+ VehicleMonitor vehicleMonitor = mVehicleMonitor.get();
+ if (vehicleMonitor != null) {
+ vehicleMonitor.mEventHandler.notifyAppViolation(pid, uid, action, violation);
+ }
+ }
+ }
+
+ private static class AppViolation {
+ public final int mPid;
+ public final int mUid;
+ public final int mAction;
+ public final int mViolation;
+
+ AppViolation(int pid, int uid, int action, int violation) {
+ mPid = pid;
+ mUid = uid;
+ mAction = action;
+ mViolation = violation;
+ }
+ }
+}
diff --git a/libvehiclemonitor/native/Android.mk b/libvehiclemonitor/native/Android.mk
new file mode 100644
index 0000000..880fcae
--- /dev/null
+++ b/libvehiclemonitor/native/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2016 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 := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+ find . -name "*.cpp" -and -not -name ".*"))
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/include \
+ packages/services/Car/libvehiclemonitor/include
+
+LOCAL_SHARED_LIBRARIES := \
+ liblog \
+ libutils \
+ libhardware \
+ libbinder
+
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbinder
+
+LOCAL_STRIP_MODULE := keep_symbols
+
+LOCAL_MODULE := libvehiclemonitor-native
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -Werror
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libvehiclemonitor/native/HandlerThread.cpp b/libvehiclemonitor/native/HandlerThread.cpp
new file mode 100644
index 0000000..5ee2715
--- /dev/null
+++ b/libvehiclemonitor/native/HandlerThread.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "HandlerThread.h"
+
+namespace android {
+
+HandlerThread::HandlerThread()
+ : mShouldQuit(false) {
+
+}
+
+HandlerThread::~HandlerThread() {
+ quit();
+}
+
+sp<Looper> HandlerThread::getLooper() {
+ Mutex::Autolock autoLock(mLock);
+ if (mLooper.get() == 0) {
+ mLooperWait.wait(mLock);
+ }
+ return mLooper;
+}
+
+status_t HandlerThread::start(const char* name, int32_t priority, size_t stack) {
+ return run(name, priority, stack);
+}
+
+void HandlerThread::quit() {
+ if (!isRunning()) {
+ return;
+ }
+ sp<Looper> looper = getLooper();
+ mLock.lock();
+ mShouldQuit = true;
+ mLock.unlock();
+ looper->wake();
+ requestExitAndWait();
+}
+
+bool HandlerThread::threadLoop() {
+ mLock.lock();
+ mLooper = Looper::prepare(0);
+ mLooperWait.broadcast();
+ mLock.unlock();
+ while (true) {
+ do {
+ Mutex::Autolock autoLock(mLock);
+ if (mShouldQuit) {
+ return false;
+ }
+ } while (false);
+ mLooper->pollOnce(-1);
+ }
+ return false;
+}
+
+
+};
diff --git a/libvehiclemonitor/native/IVehicleMonitor.cpp b/libvehiclemonitor/native/IVehicleMonitor.cpp
new file mode 100644
index 0000000..b29f665
--- /dev/null
+++ b/libvehiclemonitor/native/IVehicleMonitor.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "VehicleMonitor"
+
+#include <binder/IPCThreadState.h>
+#include <binder/Status.h>
+#include <private/android_filesystem_config.h>
+
+#include <utils/Log.h>
+
+#include <IVehicleMonitor.h>
+
+namespace android {
+enum {
+ SET_APP_PRIORITY = IBinder::FIRST_CALL_TRANSACTION,
+ SET_MONITOR_LISTENER,
+};
+
+const char IVehicleMonitor::SERVICE_NAME[] = "com.android.car.vehiclemonitor.IVehicleMonitor";
+
+// ----------------------------------------------------------------------------
+
+class BpVehicleMonitor : public BpInterface<IVehicleMonitor> {
+public:
+ explicit BpVehicleMonitor(const sp<IBinder> & impl)
+ : BpInterface<IVehicleMonitor>(impl) {
+ }
+
+ virtual status_t setAppPriority(
+ uint32_t pid, uint32_t uid, vehicle_app_priority priority) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IVehicleMonitor::getInterfaceDescriptor());
+ data.writeInt32(1);
+ data.writeInt32(pid);
+ data.writeInt32(uid);
+ data.writeInt32(priority);
+ status_t status = remote()->transact(SET_APP_PRIORITY, data, &reply);
+ if (status == NO_ERROR) {
+ int32_t exceptionCode = reply.readExceptionCode();
+ if (exceptionCode != NO_ERROR) {
+ if (exceptionCode == binder::Status::EX_SERVICE_SPECIFIC) {
+ return -EAGAIN;
+ } else if (exceptionCode == binder::Status::EX_ILLEGAL_STATE) {
+ return -ESHUTDOWN;
+ }
+ return exceptionCode;
+ }
+ }
+ return status;
+ }
+
+ virtual status_t setMonitorListener(
+ const sp<IVehicleMonitorListener> &listener) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IVehicleMonitor::getInterfaceDescriptor());
+ data.writeStrongBinder(IInterface::asBinder(listener));
+ status_t status = remote()->transact(SET_MONITOR_LISTENER, data, &reply);
+ return status;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(VehicleMonitor, IVehicleMonitor::SERVICE_NAME);
+
+// ----------------------------------------------------------------------
+
+static bool isSystemUser() {
+ uid_t uid = IPCThreadState::self()->getCallingUid();
+ switch (uid) {
+ // This list will be expanded. Only these UIDs are allowed to access vehicle monitor.
+ case AID_ROOT:
+ case AID_SYSTEM: {
+ return true;
+ } break;
+ default: {
+ ALOGE("non-system user tried access, uid %d", uid);
+ } break;
+ }
+ return false;
+}
+
+status_t BnVehicleMonitor::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+ uint32_t flags) {
+ if (!isSystemUser()) {
+ return PERMISSION_DENIED;
+ }
+ status_t r;
+ switch (code) {
+ case SET_APP_PRIORITY: {
+ CHECK_INTERFACE(IVehicleMonitor, data, reply);
+ if (data.readInt32() == 0) { // no data
+ ALOGE("null data");
+ return BAD_VALUE;
+ }
+ int32_t pid = data.readInt32();
+ int32_t uid = data.readInt32();
+ int32_t priority = data.readInt32();
+ r = setAppPriority(pid, uid, (vehicle_app_priority) priority);
+ reply->writeNoException();
+ return r;
+ } break;
+ case SET_MONITOR_LISTENER: {
+ CHECK_INTERFACE(IVehicleMonitor, data, reply);
+ sp<IVehicleMonitorListener> listener =
+ interface_cast<IVehicleMonitorListener>(data.readStrongBinder());
+ r = setMonitorListener(listener);
+ reply->writeNoException();
+ return r;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/libvehiclemonitor/native/IVehicleMonitorListener.cpp b/libvehiclemonitor/native/IVehicleMonitorListener.cpp
new file mode 100644
index 0000000..985a228
--- /dev/null
+++ b/libvehiclemonitor/native/IVehicleMonitorListener.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+
+
+#define LOG_TAG "VehicleMonitorListener"
+
+#include <memory>
+
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include <IVehicleMonitorListener.h>
+
+namespace android {
+
+enum {
+ ON_APP_VIOLATION = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpVehicleMonitorListener : public BpInterface<IVehicleMonitorListener>
+{
+ public:
+ explicit BpVehicleMonitorListener(const sp<IBinder> & impl)
+ : BpInterface<IVehicleMonitorListener>(impl) {
+ }
+
+ virtual void onAppViolation(
+ int32_t pid, int32_t uid, int32_t action, int32_t violation) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IVehicleMonitorListener::getInterfaceDescriptor());
+ data.writeInt32(1);
+ data.writeInt32(pid);
+ data.writeInt32(uid);
+ data.writeInt32(action);
+ data.writeInt32(violation);
+ remote()->transact(ON_APP_VIOLATION, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(VehicleMonitorListener, "com.android.car.vehiclemonitor.IVehicleMonitorListener");
+
+// ----------------------------------------------------------------------
+
+status_t BnVehicleMonitorListener::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+ uint32_t flags) {
+ status_t r;
+ switch (code) {
+ case ON_APP_VIOLATION: {
+ CHECK_INTERFACE(IVehicleMonitorListener, data, reply);
+ if (data.readInt32() == 0) { // java side allows passing null with this.
+ return BAD_VALUE;
+ }
+ int32_t pid = data.readInt32();
+ int32_t uid = data.readInt32();
+ int32_t action = data.readInt32();
+ int32_t violation = data.readInt32();
+ onAppViolation(pid, uid, action, violation);
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/libvehiclemonitor/native/VehicleMonitor.cpp b/libvehiclemonitor/native/VehicleMonitor.cpp
new file mode 100644
index 0000000..33235cc
--- /dev/null
+++ b/libvehiclemonitor/native/VehicleMonitor.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#define LOG_TAG "VehicleMonitor.Lib"
+
+#include <assert.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
+#include <VehicleMonitor.h>
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+static const int MAX_SERVICE_RETRY = 4;
+
+sp<VehicleMonitor> VehicleMonitor::createVehicleMonitor() {
+ sp<IBinder> binder;
+ int retry = 0;
+ while (true) {
+ binder = defaultServiceManager()->getService(String16(IVehicleMonitor::SERVICE_NAME));
+ if (binder.get() != NULL) {
+ break;
+ }
+ retry++;
+ if (retry > MAX_SERVICE_RETRY) {
+ ALOGE("cannot get VMS, will crash");
+ break;
+ }
+ }
+ assert(binder.get() != NULL);
+ sp<IVehicleMonitor> ivm(interface_cast<IVehicleMonitor>(binder));
+ sp<VehicleMonitor> vm;
+ vm = new VehicleMonitor(ivm);
+ assert(vm.get() != NULL);
+ // in case thread pool is not started, start it.
+ ProcessState::self()->startThreadPool();
+ return vm;
+}
+
+VehicleMonitor::VehicleMonitor(sp<IVehicleMonitor>& vehicleMonitor) :
+ mService(vehicleMonitor) {
+}
+
+VehicleMonitor::~VehicleMonitor() {
+ sp<IVehicleMonitor> service = getService();
+ IInterface::asBinder(service)->unlinkToDeath(this);
+}
+
+void VehicleMonitor::onFirstRef() {
+ sp<IVehicleMonitor> service = getService();
+ IInterface::asBinder(service)->linkToDeath(this);
+}
+
+status_t VehicleMonitor::setAppPriority(
+ uint32_t pid, uint32_t uid, vehicle_app_priority priority) {
+ return getService()->setAppPriority(pid, uid, priority);
+}
+
+void VehicleMonitor::binderDied(const wp<IBinder>& who) {
+ ALOGE("service died");
+ {
+ Mutex::Autolock autoLock(mLock);
+ sp<IBinder> ibinder = who.promote();
+ ibinder->unlinkToDeath(this);
+ sp<IBinder> binder = defaultServiceManager()->getService(
+ String16(IVehicleMonitor::SERVICE_NAME));
+ mService = interface_cast<IVehicleMonitor>(binder);
+ IInterface::asBinder(mService)->linkToDeath(this);
+ };
+}
+
+sp<IVehicleMonitor> VehicleMonitor::getService() {
+ Mutex::Autolock autoLock(mLock);
+ return mService;
+}
+
+}; // namespace android
diff --git a/libvehiclenetwork/native/Android.mk b/libvehiclenetwork/native/Android.mk
index e072bc6..59242cf 100644
--- a/libvehiclenetwork/native/Android.mk
+++ b/libvehiclenetwork/native/Android.mk
@@ -21,7 +21,6 @@
find . -name "*.cpp" -and -not -name ".*"))
LOCAL_C_INCLUDES += \
- libcore/include \
frameworks/base/include \
packages/services/Car/libvehiclenetwork/include
@@ -32,6 +31,8 @@
libbinder \
libprotobuf-cpp-lite
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbinder
+
LOCAL_STATIC_LIBRARIES := \
libvehiclenetworkproto-native
diff --git a/libvehiclenetwork/native/IVehicleNetwork.cpp b/libvehiclenetwork/native/IVehicleNetwork.cpp
index eb126cd..e51309d 100644
--- a/libvehiclenetwork/native/IVehicleNetwork.cpp
+++ b/libvehiclenetwork/native/IVehicleNetwork.cpp
@@ -56,7 +56,7 @@
class BpVehicleNetwork : public BpInterface<IVehicleNetwork> {
public:
- BpVehicleNetwork(const sp<IBinder> & impl)
+ explicit BpVehicleNetwork(const sp<IBinder> & impl)
: BpInterface<IVehicleNetwork>(impl) {
}
diff --git a/libvehiclenetwork/native/IVehicleNetworkHalMock.cpp b/libvehiclenetwork/native/IVehicleNetworkHalMock.cpp
index 1085a57..151d873 100644
--- a/libvehiclenetwork/native/IVehicleNetworkHalMock.cpp
+++ b/libvehiclenetwork/native/IVehicleNetworkHalMock.cpp
@@ -49,7 +49,7 @@
class BpVehicleNetworkHalMock : public BpInterface<IVehicleNetworkHalMock> {
public:
- BpVehicleNetworkHalMock(const sp<IBinder> & impl)
+ explicit BpVehicleNetworkHalMock(const sp<IBinder> & impl)
: BpInterface<IVehicleNetworkHalMock>(impl) {
}
diff --git a/libvehiclenetwork/native/IVehicleNetworkListener.cpp b/libvehiclenetwork/native/IVehicleNetworkListener.cpp
index cd79515..a8a499c 100644
--- a/libvehiclenetwork/native/IVehicleNetworkListener.cpp
+++ b/libvehiclenetwork/native/IVehicleNetworkListener.cpp
@@ -41,7 +41,7 @@
class BpVehicleNetworkListener : public BpInterface<IVehicleNetworkListener>
{
public:
- BpVehicleNetworkListener(const sp<IBinder> & impl)
+ explicit BpVehicleNetworkListener(const sp<IBinder> & impl)
: BpInterface<IVehicleNetworkListener>(impl) {
}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 28f37b3..c23958e 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -105,6 +105,12 @@
android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+ <permission
+ android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_bind_input_service"
+ android:description="@string/car_permission_desc_bind_input_service"/>
+
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4e7bb7c..e30ead8 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -56,6 +56,8 @@
and other sounds can be still played. -->
<bool name="displayOffMuteLockAllAudio">true</bool>
+ <string name="inputService">android.car.input.service/.DefaultInputService</string>
+
<string name="instrumentClusterRendererService">android.car.cluster.loggingrenderer/.LoggingClusterRenderingService</string>
<!-- Whether to enable Avtivity blocking for safety. When Activity blocking is enabled,
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 9db261c..7af6140 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -77,6 +77,11 @@
<string name="car_permission_label_bind_instrument_cluster_rendering">Instrument Cluster Rendering</string>
<string name="car_permission_desc_bind_instrument_cluster_rendering">Receive instrument cluster data</string>
+ <!-- Permission text: apps can handle input events [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_bind_input_service">Car Input Service</string>
+ <!-- Permission text: apps can handle input events [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_bind_input_service">Handle input events</string>
+
<!-- Notification messages -->
<!-- Notification text: Notification shown to the user when vehicle CAN bus fails -->
<string name="car_can_bus_failure">CAN bus failed</string>
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index c186e42..31c07d5 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -17,15 +17,27 @@
import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+import android.car.input.CarInputHandlingService;
+import android.car.input.CarInputHandlingService.InputFilter;
+import android.car.input.ICarInputListener;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.hardware.input.InputManager;
import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.CallLog.Calls;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -33,6 +45,10 @@
import com.android.car.hal.VehicleHal;
import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
public class CarInputService implements CarServiceBase, InputHalService.InputListener {
@@ -41,6 +57,7 @@
}
private static final long LONG_PRESS_TIME_MS = 1000;
+ private static final boolean DBG = false;
private final Context mContext;
private final TelecomManager mTelecomManager;
@@ -56,14 +73,76 @@
private KeyEventListener mVolumeKeyListener;
+ private ICarInputListener mCarInputListener;
+ private boolean mCarInputListenerBound = false;
+ private final Map<Integer, Set<Integer>> mHandledKeys = new HashMap<>();
+
private int mKeyEventCount = 0;
+ private final Binder mCallback = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
+ if (code == CarInputHandlingService.INPUT_CALLBACK_BINDER_CODE) {
+ data.setDataPosition(0);
+ InputFilter[] handledKeys = (InputFilter[]) data.createTypedArray(
+ InputFilter.CREATOR);
+ if (handledKeys != null) {
+ setHandledKeys(handledKeys);
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private final ServiceConnection mInputServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ if (DBG) {
+ Log.d(CarLog.TAG_INPUT, "onServiceConnected, name: "
+ + name + ", binder: " + binder);
+ }
+ mCarInputListener = ICarInputListener.Stub.asInterface(binder);
+
+ try {
+ binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> {
+ Log.w(CarLog.TAG_INPUT, "Input service died. Trying to rebind...");
+ mCarInputListener = null;
+ // Try to rebind with input service.
+ mCarInputListenerBound = bindCarInputService();
+ }), 0);
+ } catch (RemoteException e) {
+ Log.e(CarLog.TAG_INPUT, e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(CarLog.TAG_INPUT, "onServiceDisconnected, name: " + name);
+ mCarInputListener = null;
+ // Try to rebind with input service.
+ mCarInputListenerBound = bindCarInputService();
+ }
+ };
+
public CarInputService(Context context) {
mContext = context;
mTelecomManager = context.getSystemService(TelecomManager.class);
mInputManager = context.getSystemService(InputManager.class);
}
+ private synchronized void setHandledKeys(InputFilter[] handledKeys) {
+ mHandledKeys.clear();
+ for (InputFilter handledKey : handledKeys) {
+ Set<Integer> displaySet = mHandledKeys.get(handledKey.mTargetDisplay);
+ if (displaySet == null) {
+ displaySet = new HashSet<Integer>();
+ mHandledKeys.put(handledKey.mTargetDisplay, displaySet);
+ }
+ displaySet.add(handledKey.mKeyCode);
+ }
+ }
+
/**
* Set listener for listening voice assistant key event. Setting to null stops listening.
* If listener is not set, default behavior will be done for short press.
@@ -110,6 +189,7 @@
hal.setInputListener(this);
+ mCarInputListenerBound = bindCarInputService();
}
@Override
@@ -119,6 +199,10 @@
mLongVoiceAssistantKeyListener = null;
mInstrumentClusterKeyListener = null;
mKeyEventCount = 0;
+ if (mCarInputListenerBound) {
+ mContext.unbindService(mInputServiceConnection);
+ mCarInputListenerBound = false;
+ }
}
}
@@ -127,18 +211,27 @@
synchronized (this) {
mKeyEventCount++;
}
- int keyCode = event.getKeyCode();
- switch (keyCode) {
+ if (handleSystemEvent(event)) {
+ // System event handled, nothing more to do here.
+ return;
+ }
+ if (mCarInputListener != null && isCustomEventHandler(event, targetDisplay)) {
+ try {
+ mCarInputListener.onKeyEvent(event, targetDisplay);
+ } catch (RemoteException e) {
+ Log.e(CarLog.TAG_INPUT, "Error while calling car input service", e);
+ }
+ // Custom input service handled the event, nothing more to do here.
+ return;
+ }
+
+ switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOICE_ASSIST:
handleVoiceAssistKey(event);
return;
case KeyEvent.KEYCODE_CALL:
handleCallKey(event);
return;
- case KeyEvent.KEYCODE_VOLUME_UP:
- case KeyEvent.KEYCODE_VOLUME_DOWN:
- handleVolumeKey(event);
- return;
default:
break;
}
@@ -149,6 +242,25 @@
}
}
+ private synchronized boolean isCustomEventHandler(KeyEvent event, int targetDisplay) {
+ Set<Integer> displaySet = mHandledKeys.get(targetDisplay);
+ if (displaySet == null) {
+ return false;
+ }
+ return displaySet.contains(event.getKeyCode());
+ }
+
+ private boolean handleSystemEvent(KeyEvent event) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ handleVolumeKey(event);
+ return true;
+ default:
+ return false;
+ }
+ }
+
private void handleVoiceAssistKey(KeyEvent event) {
int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
@@ -263,7 +375,28 @@
@Override
public void dump(PrintWriter writer) {
writer.println("*Input Service*");
+ writer.println("mCarInputListenerBound:" + mCarInputListenerBound);
+ writer.println("mCarInputListener:" + mCarInputListener);
writer.println("mLastVoiceKeyDownTime:" + mLastVoiceKeyDownTime +
",mKeyEventCount:" + mKeyEventCount);
}
+
+ private boolean bindCarInputService() {
+ String carInputService = mContext.getString(R.string.inputService);
+ if (TextUtils.isEmpty(carInputService)) {
+ Log.i(CarLog.TAG_INPUT, "Custom input service was not configured");
+ return false;
+ }
+
+ Log.d(CarLog.TAG_INPUT, "bindCarInputService, component: " + carInputService);
+
+ Intent intent = new Intent();
+ Bundle extras = new Bundle();
+ extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback);
+ intent.putExtras(extras);
+ intent.setComponent(ComponentName.unflattenFromString(carInputService));
+ // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle service crash.
+ mContext.startService(intent);
+ return mContext.bindService(intent, mInputServiceConnection, Context.BIND_IMPORTANT);
+ }
}
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 52d123c..c290bbd 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -29,6 +29,7 @@
public static final String TAG_VENDOR_EXT = "CAR.VENDOR_EXT";
public static final String TAG_INFO = "CAR.INFO";
public static final String TAG_INPUT = "CAR.INPUT";
+ public static final String TAG_MONITORING = "CAR.MONITORING";
public static final String TAG_NAV = "CAR.NAV";
public static final String TAG_PACKAGE = "CAR.PACKAGE";
public static final String TAG_POWER = "CAR.POWER";
diff --git a/service/src/com/android/car/monitoring/CarMonitoringService.java b/service/src/com/android/car/monitoring/CarMonitoringService.java
new file mode 100644
index 0000000..3b439e1
--- /dev/null
+++ b/service/src/com/android/car/monitoring/CarMonitoringService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.monitoring;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.car.CarLog;
+import com.android.car.CarServiceBase;
+import com.android.car.SystemActivityMonitoringService;
+
+import java.io.PrintWriter;
+
+/**
+ * Service that monitors applications resource usage.
+ * @hide
+ */
+@SystemApi
+public class CarMonitoringService implements CarServiceBase {
+ private static final String TAG = CarLog.TAG_MONITORING;
+ private static final Boolean DBG = true;
+
+ private static final int MONITORING_SLEEP_TIME_MS = 30000; // Run monitoring every 30s.
+
+ private final Context mContext;
+
+ private final SystemActivityMonitoringService mSystemActivityMonitoringService;
+
+ public CarMonitoringService(Context context,
+ SystemActivityMonitoringService systemActivityMonitoringService) {
+ mContext = context;
+ mSystemActivityMonitoringService = systemActivityMonitoringService;
+ }
+
+ @Override
+ public void init() {
+ if (DBG) {
+ Log.d(TAG, "init");
+ }
+ // TODO: add periodic update to setAppPriority to monitoring native service.
+ }
+
+ @Override
+ public void release() {
+ if (DBG) {
+ Log.d(TAG, "release");
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("**" + getClass().getSimpleName() + "**");
+ // TODO
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 3ab9281..2f28765 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -32,6 +32,7 @@
<uses-permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"/>
<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application android:label="@string/app_title"
@@ -77,5 +78,10 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
+
+ <activity android:name=".setting.UsbHostManagementActivity"
+ android:theme="@android:style/Theme.Material.Light.Dialog"
+ android:launchMode="singleTop">
+ </activity>
</application>
</manifest>
diff --git a/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/usb_ic_mode.png b/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/usb_ic_mode.png
new file mode 100644
index 0000000..e69e14a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/drawable-hdpi/usb_ic_mode.png
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/usb_handler_row.xml b/tests/EmbeddedKitchenSinkApp/res/layout/usb_handler_row.xml
new file mode 100644
index 0000000..cc34e5e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/usb_handler_row.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal" >
+ <ImageView
+ android:id="@+id/usb_handler_icon"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+ android:padding="5dp" />
+ <TextView
+ android:id="@+id/usb_handler_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/usb_host.xml b/tests/EmbeddedKitchenSinkApp/res/layout/usb_host.xml
new file mode 100644
index 0000000..fbcc608
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/usb_host.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <LinearLayout
+ android:id="@+id/usb_handlers_progress"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:visibility="gone"
+ android:gravity="center">
+ <ProgressBar
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:text="@+string/usb_resolving_handlers"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+ </LinearLayout>
+ <ListView
+ android:id="@+id/usb_handlers_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index a76f194..578903d 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -204,4 +204,15 @@
<string name="portrait_activity">Portrait Activity</string>
<string name="landscpae_activity">Landscape Activity</string>
<string name="finish">Finish</string>
+
+ <!-- USB Manager Settings -->
+ <string name="usb_title">USB Devices settings</string>
+ <string name="usb_description">Customize your USB Devices settings</string>
+ <string name="usb_available_devices">Connected devices</string>
+ <string name="usb_saved_devices">Saved devices</string>
+ <string name="usb_pref_delete_title">Remove handling app for USB device</string>
+ <string name="usb_pref_delete_message">Are you sure you wan to delete dafault handling app for %1$s?</string>
+ <string name="usb_pref_delete_yes">Yes</string>
+ <string name="usb_pref_delete_cancel">Cancel</string>
+ <string name="usb_resolving_handlers">Getting supported handlers</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/xml/preference_header.xml b/tests/EmbeddedKitchenSinkApp/res/xml/preference_header.xml
index 02faa81..a3eb26a 100644
--- a/tests/EmbeddedKitchenSinkApp/res/xml/preference_header.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/xml/preference_header.xml
@@ -22,4 +22,9 @@
android:title="@string/garage_title"
android:summary="@string/garage_description" />
-</preference-headers>
\ No newline at end of file
+ <header android:fragment="com.google.android.car.kitchensink.setting.UsbManagerFragment"
+ android:icon="@drawable/usb_ic_mode"
+ android:title="@string/usb_title"
+ android:summary="@string/usb_description" />
+
+</preference-headers>
diff --git a/tests/EmbeddedKitchenSinkApp/res/xml/usb_manager_prefs.xml b/tests/EmbeddedKitchenSinkApp/res/xml/usb_manager_prefs.xml
new file mode 100644
index 0000000..b090124
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/xml/usb_manager_prefs.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.
+ -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+</PreferenceScreen>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java
index cf27e17..0ad1934 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/CarServiceSettingsActivity.java
@@ -26,6 +26,7 @@
import java.util.List;
public class CarServiceSettingsActivity extends PreferenceActivity {
+ private static final String TAG = CarServiceSettingsActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -48,7 +49,10 @@
@Override
protected boolean isValidFragment(String fragmentName) {
- if (GarageModeSettingsFragment.class.getName().equals(fragmentName)) return true;
+ if (GarageModeSettingsFragment.class.getName().equals(fragmentName)
+ || UsbManagerFragment.class.getName().equals(fragmentName)) {
+ return true;
+ }
return false;
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java
new file mode 100644
index 0000000..e025f0d
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbHostManagementActivity.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.setting.usb.UsbDeviceSettings;
+import com.google.android.car.kitchensink.setting.usb.UsbHostController;
+
+import java.util.List;
+
+/**
+ * Activity to handle USB device attached.
+ * <p>
+ * When user plugs in USB device:
+ * a) Device was used before and user selected handler for it.
+ * In this case handler will be launched.
+ * b) Device has not handler assigned. In this case supported handlers will be captured,
+ * and user will be presented with choice to assign default handler.
+ * After that handler will be lauched.
+ */
+public class UsbHostManagementActivity extends Activity
+ implements UsbHostController.UsbHostControllerCallbacks {
+ private static final String TAG = UsbHostManagementActivity.class.getSimpleName();
+ private static final boolean LOCAL_LOGD = true;
+ private static final boolean LOCAL_LOGV = true;
+
+ private HandlersAdapter mListAdapter;
+ private ListView mHandlersList;
+ private LinearLayout mProgressInfo;
+ private UsbHostController mController;
+ private PackageManager mPackageManager;
+
+ private final AdapterView.OnItemClickListener mHandlerClickListener =
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
+ UsbDeviceSettings settings = (UsbDeviceSettings) parent.getItemAtPosition(position);
+ mController.applyDeviceSettings(settings);
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.usb_host);
+ mHandlersList = (ListView) findViewById(R.id.usb_handlers_list);
+ mProgressInfo = (LinearLayout) findViewById(R.id.usb_handlers_progress);
+ mListAdapter = new HandlersAdapter(this);
+ mHandlersList.setAdapter(mListAdapter);
+ mHandlersList.setOnItemClickListener(mHandlerClickListener);
+ mController = new UsbHostController(this, this);
+ mPackageManager = getPackageManager();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mController.release();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ UsbDevice connectedDevice = getDevice();
+ if (connectedDevice != null) {
+ mController.processDevice(connectedDevice);
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public void processingStateChanged(final boolean processing) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mProgressInfo.setVisibility(processing ? View.VISIBLE : View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void titleChanged(String title) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ setTitle(title);
+ }
+ });
+ }
+
+ @Override
+ public void optionsUpdated(List<UsbDeviceSettings> options) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mListAdapter.clear();
+ mListAdapter.addAll(options);
+ }
+ });
+ }
+
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Nullable
+ private UsbDevice getDevice() {
+ if (!UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
+ return null;
+ }
+ return (UsbDevice) getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ }
+
+ private static Intent createDeviceAttachedIntent(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ private class HandlersAdapter extends ArrayAdapter<UsbDeviceSettings> {
+ class HandlerHolder {
+ public TextView mAppName;
+ public ImageView mAppIcon;
+ }
+
+ HandlersAdapter(Context context) {
+ super(context, R.layout.usb_handler_row);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View rowView = convertView;
+ if (rowView == null) {
+ rowView = getLayoutInflater().inflate(R.layout.usb_handler_row, null);
+ HandlerHolder holder = new HandlerHolder();
+ holder.mAppName = (TextView) rowView.findViewById(R.id.usb_handler_title);
+ holder.mAppIcon = (ImageView) rowView.findViewById(R.id.usb_handler_icon);
+ rowView.setTag(holder);
+ }
+
+ HandlerHolder holder = (HandlerHolder) rowView.getTag();
+ ComponentName handler = getItem(position).getHandler();
+
+ try {
+ ApplicationInfo appInfo =
+ mPackageManager.getApplicationInfo(handler.getPackageName(), 0);
+ holder.mAppName.setText(appInfo.loadLabel(mPackageManager));
+ holder.mAppIcon.setImageDrawable(appInfo.loadIcon(mPackageManager));
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Handling package not found: " + handler.getPackageName());
+ holder.mAppName.setText(handler.flattenToShortString());
+ holder.mAppIcon.setImageResource(android.R.color.transparent);
+ }
+ return rowView;
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java
new file mode 100644
index 0000000..c5656e8
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/UsbManagerFragment.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.util.Log;
+
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.setting.usb.UsbDevicePreference;
+import com.google.android.car.kitchensink.setting.usb.UsbDeviceSettings;
+import com.google.android.car.kitchensink.setting.usb.UsbSettingsStorage;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Usb manager handles management for USB device settings.
+ */
+public class UsbManagerFragment extends PreferenceFragment
+ implements UsbDevicePreference.UsbDevicePreferenceCallback {
+
+ // TODO: for produciton settings version we need to handle detach through broadcast.
+
+ private static final String TAG = UsbManagerFragment.class.getSimpleName();
+ private static final boolean LOCAL_LOGD = true;
+ private static final boolean LOCAL_LOGV = true;
+
+ private UsbSettingsStorage mUsbSettingsStorage;
+ private UsbManager mUsbManager;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.usb_manager_prefs);
+ mUsbSettingsStorage = new UsbSettingsStorage(getContext());
+ mUsbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ populateSettings();
+ }
+
+ private void populateSettings() {
+ getPreferenceScreen().removeAll();
+ HashMap<String, UsbDevice> attachedDevices = mUsbManager.getDeviceList();
+ List<UsbDeviceSettings> allSavedSettings = mUsbSettingsStorage.getAllSettings();
+ PreferenceGroup availableDevicesCategory =
+ createPreferenceCategory(1, R.string.usb_available_devices);
+ getPreferenceScreen().addPreference(availableDevicesCategory);
+
+ PreferenceGroup savedDevicesCategory =
+ createPreferenceCategory(2, R.string.usb_saved_devices);
+ getPreferenceScreen().addPreference(savedDevicesCategory);
+
+ for (UsbDevice attachedDevice : attachedDevices.values()) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "Attached device: " + attachedDevice);
+ }
+ if (attachedDevice.getSerialNumber() == null) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Attached device: " + attachedDevice + " misseshave serial number");
+ }
+ continue;
+ }
+ UsbDeviceSettings deviceSettings = getSavedSetting(allSavedSettings, attachedDevice);
+ if (deviceSettings != null) {
+ // This might be slow if there are a lot of settings.
+ allSavedSettings.remove(deviceSettings);
+ } else {
+ deviceSettings = UsbDeviceSettings.constructSettings(attachedDevice);
+ }
+ UsbDevicePreference preference = new UsbDevicePreference(
+ getContext(), deviceSettings, this);
+ availableDevicesCategory.addPreference(preference);
+ }
+ availableDevicesCategory.setEnabled(availableDevicesCategory.getPreferenceCount() > 0);
+
+ for (UsbDeviceSettings savedSettings : allSavedSettings) {
+ UsbDevicePreference preference = new UsbDevicePreference(
+ getContext(), savedSettings, this);
+ savedDevicesCategory.addPreference(preference);
+ }
+ savedDevicesCategory.setEnabled(savedDevicesCategory.getPreferenceCount() > 0);
+ }
+
+ private PreferenceGroup createPreferenceCategory(int order, int titleId) {
+ PreferenceGroup preferenceGroup = new PreferenceCategory(getContext());
+ preferenceGroup.setTitle(titleId);
+ preferenceGroup.setOrder(order);
+ preferenceGroup.setSelectable(false);
+ return preferenceGroup;
+ }
+
+ @Nullable
+ private UsbDeviceSettings getSavedSetting(List<UsbDeviceSettings> settings, UsbDevice device) {
+ for (UsbDeviceSettings savedSetting : settings) {
+ if (savedSetting.matchesDevice(device)) {
+ return savedSetting;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onUsbDevicePreferenceDelete(Preference preference, UsbDeviceSettings settings) {
+ mUsbSettingsStorage.deleteSettings(
+ settings.getSerialNumber(), settings.getVid(), settings.getPid());
+ populateSettings();
+ }
+
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java
new file mode 100644
index 0000000..8907dc6
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/AoapInterface.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.util.Log;
+
+import java.io.IOException;
+
+final class AoapInterface {
+ /**
+ * Use Google Vendor ID when in accessory mode
+ */
+ public static final int USB_ACCESSORY_VENDOR_ID = 0x18D1;
+
+ /**
+ * Product ID to use when in accessory mode
+ */
+ public static final int USB_ACCESSORY_PRODUCT_ID = 0x2D00;
+
+ /**
+ * Product ID to use when in accessory mode and adb is enabled
+ */
+ public static final int USB_ACCESSORY_ADB_PRODUCT_ID = 0x2D01;
+
+ /**
+ * Indexes for strings sent by the host via ACCESSORY_SEND_STRING
+ */
+ public static final int ACCESSORY_STRING_MANUFACTURER = 0;
+ public static final int ACCESSORY_STRING_MODEL = 1;
+ public static final int ACCESSORY_STRING_DESCRIPTION = 2;
+ public static final int ACCESSORY_STRING_VERSION = 3;
+ public static final int ACCESSORY_STRING_URI = 4;
+ public static final int ACCESSORY_STRING_SERIAL = 5;
+
+ /**
+ * Control request for retrieving device's protocol version
+ *
+ * requestType: USB_DIR_IN | USB_TYPE_VENDOR
+ * request: ACCESSORY_GET_PROTOCOL
+ * value: 0
+ * index: 0
+ * data version number (16 bits little endian)
+ * 1 for original accessory support
+ * 2 adds HID and device to host audio support
+ */
+ public static final int ACCESSORY_GET_PROTOCOL = 51;
+
+ /**
+ * Control request for host to send a string to the device
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_SEND_STRING
+ * value: 0
+ * index: string ID
+ * data zero terminated UTF8 string
+ *
+ * The device can later retrieve these strings via the
+ * ACCESSORY_GET_STRING_* ioctls
+ */
+ public static final int ACCESSORY_SEND_STRING = 52;
+
+ /**
+ * Control request for starting device in accessory mode.
+ * The host sends this after setting all its strings to the device.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_START
+ * value: 0
+ * index: 0
+ * data none
+ */
+ public static final int ACCESSORY_START = 53;
+
+ /**
+ * Max payload size for AOAP. Limited by driver.
+ */
+ public static final int MAX_PAYLOAD_SIZE = 16384;
+
+ /**
+ * Accessory write timeout.
+ */
+ public static final int AOAP_TIMEOUT_MS = 2000;
+
+ private static final String TAG = AoapInterface.class.getSimpleName();
+
+ public static int getProtocol(UsbDeviceConnection conn) {
+ byte[] buffer = new byte[2];
+ int len = conn.controlTransfer(
+ UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
+ ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, AOAP_TIMEOUT_MS);
+ if (len != 2) {
+ return -1;
+ }
+ return (buffer[1] << 8) | buffer[0];
+ }
+
+ public static void sendString(UsbDeviceConnection conn, int index, String string)
+ throws IOException {
+ byte[] buffer = (string + "\0").getBytes();
+ int len = conn.controlTransfer(
+ UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
+ ACCESSORY_SEND_STRING, 0, index,
+ buffer, buffer.length, AOAP_TIMEOUT_MS);
+ if (len != buffer.length) {
+ throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
+ } else {
+ Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
+ }
+ }
+
+ public static void sendAoapStart(UsbDeviceConnection conn) throws IOException {
+ int len = conn.controlTransfer(
+ UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
+ ACCESSORY_START, 0, 0, null, 0, AOAP_TIMEOUT_MS);
+ if (len < 0) {
+ throw new IOException("Control transfer for accessory start failed:" + len);
+ }
+ }
+
+ public static boolean isDeviceInAoapMode(UsbDevice device) {
+ if (device == null) {
+ return false;
+ }
+ final int vid = device.getVendorId();
+ final int pid = device.getProductId();
+ return vid == USB_ACCESSORY_VENDOR_ID
+ && (pid == USB_ACCESSORY_PRODUCT_ID || pid == USB_ACCESSORY_ADB_PRODUCT_ID);
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java
new file mode 100644
index 0000000..b86c324
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceHandlerResolver.java
@@ -0,0 +1,820 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.car.IUsbAoapSupportCheckService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Resolves supported handlers for USB device.
+ */
+public final class UsbDeviceHandlerResolver
+ implements UsbDeviceStateController.UsbDeviceStateListener {
+ private static final String TAG = UsbDeviceHandlerResolver.class.getSimpleName();
+ private static final boolean LOCAL_LOGD = true;
+
+ private static final int MODE_OFF = 0;
+ private static final int MODE_PROBE = 1;
+ private static final int MODE_PROBE_AOAP = 2;
+ private static final int MODE_DISPATCH = 3;
+
+ /**
+ * Callbacks for device reolver.
+ */
+ public interface UsbDeviceHandlerResolverCallback {
+ /** Handlers are reolved */
+ void onHandlersResolveCompleted(
+ UsbDevice device, List<UsbDeviceSettings> availableSettings);
+ /** Device was dispatched */
+ void onDeviceDispatched();
+ }
+
+ private final UsbManager mUsbManager;
+ private final PackageManager mPackageManager;
+ private final UsbDeviceHandlerResolverCallback mDeviceCallback;
+ private final Context mContext;
+ private final HandlerThread mHandlerThread;
+ private final UsbDeviceResolverHandler mHandler;
+ private final UsbDeviceStateController mStateController;
+ private final Queue<Pair<ResolveInfo, DeviceFilter>> mActiveDeviceOptions = new LinkedList<>();
+ private final List<UsbDeviceSettings> mActiveDeviceSettings = new ArrayList<>();
+
+ private String mActiveDeviceSerial;
+ private UsbDevice mActiveUsbDevice;
+ private UsbDeviceSettings mBaseSettings;
+ private int mDeviceMode = MODE_OFF;
+ private Intent mDispatchIntent;
+ private int mDispatchUid;
+ private IUsbAoapSupportCheckService mIUsbAoapSupportCheckService;
+ private boolean mBound;
+
+ // This class is used to describe a USB device.
+ // When used in HashMaps all values must be specified,
+ // but wildcards can be used for any of the fields in
+ // the package meta-data.
+ private static class DeviceFilter {
+ // USB Vendor ID (or -1 for unspecified)
+ public final int mVendorId;
+ // USB Product ID (or -1 for unspecified)
+ public final int mProductId;
+ // USB device or interface class (or -1 for unspecified)
+ public final int mClass;
+ // USB device subclass (or -1 for unspecified)
+ public final int mSubclass;
+ // USB device protocol (or -1 for unspecified)
+ public final int mProtocol;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ // USB device in AOAP mode manufacturer
+ public final String mAoapManufacturer;
+ // USB device in AOAP mode modeal
+ public final String mAoapModel;
+ // USB device in AOAP mode description string
+ public final String mAoapDescription;
+ // USB device in AOAP mode version
+ public final String mAoapVersion;
+ // USB device in AOAP mode URI
+ public final String mAoapUri;
+ // USB device in AOAP mode serial
+ public final String mAoapSerial;
+ // USB device in AOAP mode verification service
+ public final String mAoapService;
+
+ DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum,
+ String aoapManufacturer, String aoapModel, String aoapDescription,
+ String aoapVersion, String aoapUri, String aoapSerial,
+ String aoapService) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+
+ mAoapManufacturer = aoapManufacturer;
+ mAoapModel = aoapModel;
+ mAoapDescription = aoapDescription;
+ mAoapVersion = aoapVersion;
+ mAoapUri = aoapUri;
+ mAoapSerial = aoapSerial;
+ mAoapService = aoapService;
+ }
+
+ DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ mAoapManufacturer = null;
+ mAoapModel = null;
+ mAoapDescription = null;
+ mAoapVersion = null;
+ mAoapUri = null;
+ mAoapSerial = null;
+ mAoapService = null;
+ }
+
+ public static DeviceFilter read(XmlPullParser parser, boolean aoapData)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ String aoapManufacturer = null;
+ String aoapModel = null;
+ String aoapDescription = null;
+ String aoapVersion = null;
+ String aoapUri = null;
+ String aoapSerial = null;
+ String aoapService = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if (!aoapData && "manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if (!aoapData && "product-name".equals(name)) {
+ productName = value;
+ } else if (!aoapData && "serial-number".equals(name)) {
+ serialNumber = value;
+ } else if (aoapData && "manufacturer".equals(name)) {
+ aoapManufacturer = value;
+ } else if (aoapData && "model".equals(name)) {
+ aoapModel = value;
+ } else if (aoapData && "description".equals(name)) {
+ aoapDescription = value;
+ } else if (aoapData && "version".equals(name)) {
+ aoapVersion = value;
+ } else if (aoapData && "uri".equals(name)) {
+ aoapUri = value;
+ } else if (aoapData && "serial".equals(name)) {
+ aoapSerial = value;
+ } else if (aoapData && "service".equals(name)) {
+ aoapService = value;
+ } else if (!aoapData) {
+ int intValue = -1;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0'
+ && (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber, aoapManufacturer,
+ aoapModel, aoapDescription, aoapVersion, aoapUri, aoapSerial,
+ aoapService);
+ }
+
+ private boolean matches(int clasz, int subclass, int protocol) {
+ return ((mClass == -1 || clasz == mClass)
+ && (mSubclass == -1 || subclass == mSubclass)
+ && (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ public boolean isAoap() {
+ return (mVendorId == AoapInterface.USB_ACCESSORY_VENDOR_ID
+ && mProductId == AoapInterface.USB_ACCESSORY_PRODUCT_ID);
+ }
+
+ public boolean matches(UsbDevice device) {
+ if (mVendorId != -1 && device.getVendorId() != mVendorId) {
+ return false;
+ }
+ if (mProductId != -1 && device.getProductId() != mProductId) {
+ return false;
+ }
+ if (mManufacturerName != null && device.getManufacturerName() == null) {
+ return false;
+ }
+ if (mProductName != null && device.getProductName() == null) {
+ return false;
+ }
+ if (mSerialNumber != null && device.getSerialNumber() == null) {
+ return false;
+ }
+ if (mManufacturerName != null && device.getManufacturerName() != null
+ && !mManufacturerName.equals(device.getManufacturerName())) {
+ return false;
+ }
+ if (mProductName != null && device.getProductName() != null
+ && !mProductName.equals(device.getProductName())) {
+ return false;
+ }
+ if (mSerialNumber != null && device.getSerialNumber() != null
+ && !mSerialNumber.equals(device.getSerialNumber())) {
+ return false;
+ }
+
+ // check device class/subclass/protocol
+ if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+ device.getDeviceProtocol())) {
+ return true;
+ }
+
+ // if device doesn't match, check the interfaces
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+ intf.getInterfaceProtocol())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mVendorId == -1 || mProductId == -1
+ || mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+ return false;
+ }
+ if (obj instanceof DeviceFilter) {
+ DeviceFilter filter = (DeviceFilter) obj;
+
+ if (filter.mVendorId != mVendorId
+ || filter.mProductId != mProductId
+ || filter.mClass != mClass
+ || filter.mSubclass != mSubclass
+ || filter.mProtocol != mProtocol) {
+ return false;
+ }
+ if ((filter.mManufacturerName != null && mManufacturerName == null)
+ || (filter.mManufacturerName == null && mManufacturerName != null)
+ || (filter.mProductName != null && mProductName == null)
+ || (filter.mProductName == null && mProductName != null)
+ || (filter.mSerialNumber != null && mSerialNumber == null)
+ || (filter.mSerialNumber == null && mSerialNumber != null)) {
+ return false;
+ }
+ if ((filter.mManufacturerName != null && mManufacturerName != null
+ && !mManufacturerName.equals(filter.mManufacturerName))
+ || (filter.mProductName != null && mProductName != null
+ && !mProductName.equals(filter.mProductName))
+ || (filter.mSerialNumber != null && mSerialNumber != null
+ && !mSerialNumber.equals(filter.mSerialNumber))) {
+ return false;
+ }
+ return true;
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice) obj;
+ if (device.getVendorId() != mVendorId
+ || device.getProductId() != mProductId
+ || device.getDeviceClass() != mClass
+ || device.getDeviceSubclass() != mSubclass
+ || device.getDeviceProtocol() != mProtocol) {
+ return false;
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null)
+ || (mManufacturerName == null && device.getManufacturerName() != null)
+ || (mProductName != null && device.getProductName() == null)
+ || (mProductName == null && device.getProductName() != null)
+ || (mSerialNumber != null && device.getSerialNumber() == null)
+ || (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return false;
+ }
+ if ((device.getManufacturerName() != null
+ && !mManufacturerName.equals(device.getManufacturerName()))
+ || (device.getProductName() != null
+ && !mProductName.equals(device.getProductName()))
+ || (device.getSerialNumber() != null
+ && !mSerialNumber.equals(device.getSerialNumber()))) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (((mVendorId << 16) | mProductId)
+ ^ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId
+ + ",mClass=" + mClass + ",mSubclass=" + mSubclass
+ + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName
+ + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + "]";
+ }
+ }
+
+ private final ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ Log.i(TAG, "onServiceConnected: " + className);
+ mHandler.requestOnServiceConnectionStateChanged(
+ IUsbAoapSupportCheckService.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ Log.i(TAG, "onServiceDisconnected: " + className);
+ mHandler.requestOnServiceConnectionStateChanged(null);
+ }
+ };
+
+ public UsbDeviceHandlerResolver(UsbManager manager, Context context,
+ UsbDeviceHandlerResolverCallback deviceListener) {
+ mUsbManager = manager;
+ mContext = context;
+ mDeviceCallback = deviceListener;
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+ mPackageManager = context.getPackageManager();
+ mStateController = new UsbDeviceStateController(context, this, manager);
+ mStateController.init();
+ }
+
+ /**
+ * Releases current object.
+ */
+ public void release() {
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
+ if (mStateController != null) {
+ mStateController.release();
+ }
+ }
+
+ /**
+ * Resolves handlers for USB device.
+ */
+ public void resolve(UsbDevice device) {
+ mHandler.requestResolveHandlers(device);
+ }
+
+ /**
+ * Dispatches device to component.
+ */
+ public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap:" + inAoap);
+ }
+
+ mActiveUsbDevice = device;
+ mDeviceMode = MODE_DISPATCH;
+ ActivityInfo activityInfo;
+ try {
+ activityInfo = mPackageManager.getActivityInfo(component, PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Activity not found: " + component);
+ return false;
+ }
+
+ Intent intent = createDeviceAttachedIntent(device);
+ if (inAoap) {
+ DeviceFilter filter = packageMatches(activityInfo, intent.getAction(), device, true);
+ intent.setComponent(component);
+ mDispatchIntent = intent;
+ mDispatchUid = activityInfo.applicationInfo.uid;
+ if (filter != null) {
+ requestAoapSwitch(filter);
+ return true;
+ }
+ }
+
+ intent.setComponent(component);
+ mUsbManager.grantPermission(device, activityInfo.applicationInfo.uid);
+
+ mContext.startActivity(intent);
+ mHandler.requestCompleteDeviceDispatch();
+ return true;
+ }
+
+ private static Intent createDeviceAttachedIntent(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ private void doHandleResolveHandlers(
+ UsbDevice device) {
+ mActiveDeviceSettings.clear();
+ mActiveDeviceOptions.clear();
+ mActiveUsbDevice = device;
+ mDeviceMode = MODE_PROBE;
+
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleResolveHandlers: " + device);
+ }
+ boolean maySupportAoap = UsbUtil.possiblyAndroid(device);
+ boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
+
+ Intent intent = createDeviceAttachedIntent(device);
+ List<Pair<ResolveInfo, DeviceFilter>> matches = getDeviceMatches(device, intent, false);
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "matches size: " + matches.size());
+ }
+ for (Pair<ResolveInfo, DeviceFilter> info : matches) {
+ UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(mActiveUsbDevice);
+ setting.setHandler(
+ new ComponentName(
+ info.first.activityInfo.packageName, info.first.activityInfo.name));
+ mActiveDeviceSettings.add(setting);
+ }
+ mBaseSettings = UsbDeviceSettings.constructSettings(mActiveUsbDevice);
+ if (!AoapInterface.isDeviceInAoapMode(device) && maySupportAoap) {
+ mActiveDeviceOptions.addAll(getDeviceMatches(device, intent, true));
+ handleTryNextAoapMode();
+ } else {
+ doHandleCompleteDeviceProbing();
+ }
+ }
+
+ private void handleTryNextAoapMode() {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "handleTryNextAoapMode");
+ }
+ Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
+ if (option == null) {
+ mHandler.requestCompleteDeviceProbing();
+ return;
+ }
+ requestAoapSwitch(option.second);
+ }
+
+ private void requestAoapSwitch(DeviceFilter filter) {
+ UsbDeviceStateController.AoapSwitchRequest request =
+ new UsbDeviceStateController.AoapSwitchRequest(
+ mActiveUsbDevice,
+ filter.mAoapManufacturer,
+ filter.mAoapModel,
+ filter.mAoapDescription,
+ filter.mAoapVersion,
+ filter.mAoapUri,
+ filter.mAoapSerial);
+ mStateController.startAoap(request);
+ }
+
+ private void doHandleCompleteDeviceProbing() {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleCompleteDeviceProbing");
+ }
+ mDeviceCallback.onHandlersResolveCompleted(mActiveUsbDevice, mActiveDeviceSettings);
+ stopDeviceProcessing(mActiveUsbDevice);
+ mActiveUsbDevice = null;
+ mBaseSettings = null;
+ mDeviceMode = MODE_OFF;
+ }
+
+ private void doHandleAoapStartComplete(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleAoapStartComplete:" + device + " mode: " + MODE_DISPATCH);
+ }
+ if (mDeviceMode == MODE_DISPATCH && mDispatchIntent != null) {
+ mDispatchIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ mUsbManager.grantPermission(device, mDispatchUid);
+ mContext.startActivity(mDispatchIntent);
+ mDispatchIntent = null;
+ mDispatchUid = 0;
+ mDeviceMode = MODE_OFF;
+ mDeviceCallback.onDeviceDispatched();
+ return;
+ }
+ mActiveUsbDevice = device;
+ mDeviceMode = MODE_PROBE_AOAP;
+ if (device == null) {
+ mActiveDeviceOptions.poll();
+ handleTryNextAoapMode();
+ }
+
+ Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
+ if (option == null) {
+ Log.w(TAG, "No more options left.");
+ }
+ DeviceFilter filter = option.second;
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(ComponentName.unflattenFromString(option.second.mAoapService));
+ boolean bound = mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
+ if (bound) {
+ mHandler.requestServiceConnectionTimeout();
+ } else {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "Failed to bind to the service");
+ }
+ mStateController.startDeviceReset(mActiveUsbDevice);
+ }
+ }
+
+ private void doHandleDeviceResetComplete(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleDeviceResetComplete:" + device);
+ }
+ mActiveDeviceOptions.poll();
+ mActiveUsbDevice = device;
+ mDeviceMode = MODE_PROBE;
+ handleTryNextAoapMode();
+ }
+
+ private void doHandleServiceConnectionStateChanged(IUsbAoapSupportCheckService service) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleServiceConnectionStateChanged: " + service);
+ }
+ mBound = service != null;
+ mIUsbAoapSupportCheckService = service;
+ if (mBound && mActiveUsbDevice != null && mDeviceMode == MODE_PROBE_AOAP) {
+ boolean deviceSupported = false;
+ try {
+ deviceSupported =
+ mIUsbAoapSupportCheckService.isDeviceSupported(mActiveUsbDevice);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Call to remote service failed", e);
+ }
+ if (deviceSupported) {
+ Pair<ResolveInfo, DeviceFilter> option = mActiveDeviceOptions.peek();
+
+ UsbDeviceSettings setting = UsbDeviceSettings.constructSettings(mBaseSettings);
+ setting.setHandler(
+ new ComponentName(
+ option.first.activityInfo.packageName, option.first.activityInfo.name));
+ setting.setAoap(true);
+ mActiveDeviceSettings.add(setting);
+ }
+ mContext.unbindService(mConnection);
+ mBound = false;
+ mIUsbAoapSupportCheckService = null;
+ mStateController.startDeviceReset(mActiveUsbDevice);
+ } else if (mActiveUsbDevice != null && mDeviceMode == MODE_PROBE_AOAP) {
+ mStateController.startDeviceReset(mActiveUsbDevice);
+ } else {
+ handleTryNextAoapMode();
+ }
+ }
+
+ private List<Pair<ResolveInfo, DeviceFilter>> getDeviceMatches(
+ UsbDevice device, Intent intent, boolean forAoap) {
+ List<Pair<ResolveInfo, DeviceFilter>> matches = new ArrayList<>();
+ List<ResolveInfo> resolveInfos =
+ mPackageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ DeviceFilter filter = packageMatches(resolveInfo.activityInfo,
+ intent.getAction(), device, forAoap);
+ if (filter != null) {
+ matches.add(Pair.create(resolveInfo, filter));
+ }
+ }
+ return matches;
+ }
+
+ private DeviceFilter packageMatches(ActivityInfo ai, String metaDataName, UsbDevice device,
+ boolean forAoap) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "packageMatches ai: " + ai + "metaDataName: " + metaDataName + " forAoap: "
+ + forAoap);
+ }
+ String filterTagName = forAoap ? "usb-aoap-accessory" : "usb-device";
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
+ if (parser == null) {
+ Log.w(TAG, "no meta-data for " + ai);
+ return null;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (device != null && filterTagName.equals(tagName)) {
+ DeviceFilter filter = DeviceFilter.read(parser, forAoap);
+ if (forAoap || filter.matches(device)) {
+ return filter;
+ }
+ }
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + ai.toString(), e);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ return null;
+ }
+
+ @Override
+ public void onDeviceResetComplete(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "onDeviceResetComplete: " + device);
+ }
+ mHandler.requestOnDeviceResetComplete(device);
+ }
+
+ @Override
+ public void onAoapStartComplete(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "onAoapStartComplete: " + device);
+ }
+ mHandler.requestOnAoapStartComplete(device);
+ }
+
+ @Override
+ public void onAoapStartFailed(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "onAoapStartFailed: " + device);
+ }
+ mActiveDeviceOptions.poll();
+ handleTryNextAoapMode();
+ }
+
+ private boolean isDeviceProcessing(UsbDevice device) {
+ return mActiveDeviceSerial != null
+ && mActiveDeviceSerial.equals(device.getSerialNumber());
+ }
+
+ private boolean stopDeviceProcessing(UsbDevice device) {
+ if (device == null || device.getSerialNumber().equals(mActiveDeviceSerial)) {
+ mActiveDeviceSerial = null;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean startDeviceProcessing(UsbDevice device) {
+ if (mActiveDeviceSerial != null) {
+ return false;
+ } else {
+ mActiveDeviceSerial = device.getSerialNumber();
+ return true;
+ }
+ }
+
+ private class UsbDeviceResolverHandler extends Handler {
+ private static final int MSG_RESOLVE_HANDLERS = 0;
+ private static final int MSG_DEVICE_RESET_COMPLETE = 1;
+ private static final int MSG_AOAP_START_COMPLETE = 2;
+ private static final int MSG_SERVICE_CONNECTION_STATE_CHANGE = 3;
+ private static final int MSG_SERVICE_CONNECTION_TIMEOUT = 4;
+ private static final int MSG_COMPLETE_PROBING = 5;
+ private static final int MSG_COMPLETE_DISPATCH = 6;
+
+ private static final long RESCHEDULE_TIMEOUT_MS = 100;
+ private static final long CONNECT_TIMEOUT_MS = 5000;
+ private static final long FINISH_PROBING_TIMEOUT_MS = 200;
+
+ private UsbDeviceResolverHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void requestResolveHandlers(UsbDevice device) {
+ Message msg = obtainMessage(MSG_RESOLVE_HANDLERS, device);
+ sendMessage(msg);
+ }
+
+ public void requestOnAoapStartComplete(UsbDevice device) {
+ sendMessage(obtainMessage(MSG_AOAP_START_COMPLETE, device));
+ }
+
+ public void requestOnDeviceResetComplete(UsbDevice device) {
+ sendMessage(obtainMessage(MSG_DEVICE_RESET_COMPLETE, device));
+ }
+
+ public void requestOnServiceConnectionStateChanged(IUsbAoapSupportCheckService service) {
+ sendMessage(obtainMessage(MSG_SERVICE_CONNECTION_STATE_CHANGE, service));
+ }
+
+ public void requestServiceConnectionTimeout() {
+ sendEmptyMessageDelayed(MSG_SERVICE_CONNECTION_TIMEOUT, CONNECT_TIMEOUT_MS);
+ }
+
+ public void requestCompleteDeviceProbing() {
+ sendEmptyMessageDelayed(MSG_COMPLETE_PROBING, FINISH_PROBING_TIMEOUT_MS);
+ }
+
+ public void requestCompleteDeviceDispatch() {
+ sendEmptyMessage(MSG_COMPLETE_DISPATCH);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESOLVE_HANDLERS:
+ UsbDevice device = (UsbDevice) msg.obj;
+ // if this device is already being processed - drop the request.
+ if (!isDeviceProcessing(device)) {
+ if (startDeviceProcessing(device)) {
+ doHandleResolveHandlers(device);
+ } else {
+ // Reschedule this device for processing at later time.
+ sendMessageDelayed(msg, RESCHEDULE_TIMEOUT_MS);
+ }
+ } else {
+ Log.i(TAG, "Device is already being processed: " + device);
+ }
+ break;
+ case MSG_AOAP_START_COMPLETE:
+ doHandleAoapStartComplete((UsbDevice) msg.obj);
+ break;
+ case MSG_DEVICE_RESET_COMPLETE:
+ doHandleDeviceResetComplete((UsbDevice) msg.obj);
+ break;
+ case MSG_SERVICE_CONNECTION_STATE_CHANGE:
+ removeMessages(MSG_SERVICE_CONNECTION_TIMEOUT);
+ doHandleServiceConnectionStateChanged((IUsbAoapSupportCheckService) msg.obj);
+ break;
+ case MSG_SERVICE_CONNECTION_TIMEOUT:
+ Log.i(TAG, "Service connection timeout");
+ doHandleServiceConnectionStateChanged(null);
+ break;
+ case MSG_COMPLETE_PROBING:
+ doHandleCompleteDeviceProbing();
+ break;
+ case MSG_COMPLETE_DISPATCH:
+ mDeviceCallback.onDeviceDispatched();
+ break;
+ default:
+ Log.w(TAG, "Unsupported message: " + msg);
+ }
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java
new file mode 100644
index 0000000..62f624e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDevicePreference.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.preference.Preference;
+
+import com.google.android.car.kitchensink.R;
+
+/**
+ * Setting preferabce used for USB devices.
+ */
+public final class UsbDevicePreference extends Preference
+ implements Preference.OnPreferenceClickListener {
+
+ /**
+ * Callbacks to handle preference changes.
+ */
+ public interface UsbDevicePreferenceCallback {
+ /** Preference deleted */
+ void onUsbDevicePreferenceDelete(Preference preference, UsbDeviceSettings settings);
+ }
+
+ private static final String TAG = UsbDevicePreference.class.getSimpleName();
+
+ private final UsbDeviceSettings mUsbDeviceSettings;
+ private final UsbDevicePreferenceCallback mCallback;
+
+ public UsbDevicePreference(Context context, UsbDeviceSettings usbDeviceSettings,
+ UsbDevicePreferenceCallback callback) {
+ super(context);
+ mCallback = callback;
+ mUsbDeviceSettings = usbDeviceSettings;
+ setTitle(usbDeviceSettings.getDeviceName());
+ if (usbDeviceSettings.getHandler() != null) {
+ setSummary(usbDeviceSettings.getHandler().flattenToShortString());
+ }
+ setOnPreferenceClickListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceClick(final Preference preference) {
+ new AlertDialog.Builder(getContext())
+ .setTitle(R.string.usb_pref_delete_title)
+ .setMessage(String.format(
+ getContext().getResources().getString(R.string.usb_pref_delete_message),
+ mUsbDeviceSettings.getDeviceName()))
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(R.string.usb_pref_delete_yes,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mCallback.onUsbDevicePreferenceDelete(
+ preference, mUsbDeviceSettings);
+ }})
+ .setNegativeButton(R.string.usb_pref_delete_cancel, null)
+ .show();
+ return true;
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java
new file mode 100644
index 0000000..7d4dc8a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceSettings.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.content.ComponentName;
+import android.hardware.usb.UsbDevice;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Settings for USB device.
+ * @hide
+ */
+public final class UsbDeviceSettings {
+
+ private final String mSerialNumber;
+ private final int mVid;
+ private final int mPid;
+ private String mDeviceName;
+ private ComponentName mHandler;
+ private boolean mAoap;
+
+ UsbDeviceSettings(String serialNumber, int vid, int pid) {
+ Preconditions.checkNotNull(serialNumber);
+
+ mSerialNumber = serialNumber;
+ mVid = vid;
+ mPid = pid;
+ }
+
+ public String getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ public int getVid() {
+ return mVid;
+ }
+
+ public int getPid() {
+ return mPid;
+ }
+
+ public void setDeviceName(String deviceName) {
+ mDeviceName = deviceName;
+ }
+
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ public void setHandler(ComponentName handler) {
+ mHandler = handler;
+ }
+
+ public ComponentName getHandler() {
+ return mHandler;
+ }
+
+ public void setAoap(boolean aoap) {
+ mAoap = aoap;
+ }
+
+ public boolean getAoap() {
+ return mAoap;
+ }
+
+ @Override
+ public String toString() {
+ return "UsbDeviceSettings{serial=" + getSerialNumber() + ", vid=" + mVid + "pid=" + mPid
+ + "name=" + getDeviceName() + ", handler=" + getHandler().toString()
+ + "aoap=" + getAoap() + "}";
+ }
+
+ /**
+ * Checks if setting matches {@code UsbDevice}.
+ */
+ public boolean matchesDevice(UsbDevice device) {
+ return (getSerialNumber().equals(device.getSerialNumber())
+ && getVid() == device.getVendorId()
+ && getPid() == device.getProductId());
+ }
+
+ /**
+ * Creates settings from {@code UsbDevice}.
+ */
+ public static UsbDeviceSettings constructSettings(UsbDevice device) {
+ UsbDeviceSettings settings = new UsbDeviceSettings(
+ device.getSerialNumber(), device.getVendorId(), device.getProductId());
+ settings.setDeviceName(device.getProductName());
+ return settings;
+ }
+
+ /**
+ * Creates settings from other settings.
+ * <p>
+ * Only basic properties are inherited.
+ */
+ public static UsbDeviceSettings constructSettings(UsbDeviceSettings origSettings) {
+ UsbDeviceSettings settings = new UsbDeviceSettings(
+ origSettings.getSerialNumber(), origSettings.getVid(), origSettings.getPid());
+ settings.setDeviceName(origSettings.getDeviceName());
+ return settings;
+ }
+
+ /**
+ * Creates settings.
+ */
+ public static UsbDeviceSettings constructSettings(String serialNumber, int vid, int pid,
+ String deviceName, ComponentName handler, boolean aoap) {
+ UsbDeviceSettings settings = new UsbDeviceSettings(serialNumber, vid, pid);
+ settings.setDeviceName(deviceName);
+ settings.setHandler(handler);
+ settings.setAoap(aoap);
+ return settings;
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java
new file mode 100644
index 0000000..85a3e0a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbDeviceStateController.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+/**
+ * Controller to change device into AOAP mode and back.
+ */
+class UsbDeviceStateController {
+ /**
+ * Listener for USB device mode controller.
+ */
+ public interface UsbDeviceStateListener {
+ void onDeviceResetComplete(UsbDevice device);
+ void onAoapStartComplete(UsbDevice device);
+ void onAoapStartFailed(UsbDevice device);
+ }
+
+ private static final String TAG = UsbDeviceStateController.class.getSimpleName();
+ private static final boolean LOCAL_LOGD = true;
+
+ private static final int USB_RESET_RETRY_COUNT = 10;
+ private static final int MAX_USB_DETACH_CHANGE_WAIT = 50;
+ private static final int MAX_USB_ATTACH_CHANGE_WAIT = 5;
+ private static final long USB_STATE_DETACH_WAIT_TIMEOUT_MS = 20;
+ private static final long USB_STATE_ATTACH_WAIT_TIMEOUT_MS = 500;
+
+ private final Context mContext;
+ private final UsbDeviceStateListener mListener;
+ private final UsbManager mUsbManager;
+ private final HandlerThread mHandlerThread;
+ private final UsbStateHandler mHandler;
+ private final UsbDeviceBroadcastReceiver mUsbStateBroadcastReceiver;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private final Object mUsbConnectionChangeWait = new Object();
+ private final LinkedList<UsbDevice> mDevicesRemoved = new LinkedList<>();
+ private final LinkedList<UsbDevice> mDevicesAdded = new LinkedList<>();
+ private boolean mShouldQuit = false;
+
+ UsbDeviceStateController(Context context, UsbDeviceStateListener listener,
+ UsbManager usbManager) {
+ mContext = context;
+ mListener = listener;
+ mUsbManager = usbManager;
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mCloseGuard.open("release");
+ mHandler = new UsbStateHandler(mHandlerThread.getLooper());
+ mUsbStateBroadcastReceiver = new UsbDeviceBroadcastReceiver();
+ }
+
+ public void init() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ mContext.registerReceiver(mUsbStateBroadcastReceiver, filter);
+ }
+
+ public void release() {
+ mCloseGuard.close();
+ mContext.unregisterReceiver(mUsbStateBroadcastReceiver);
+ synchronized (mUsbConnectionChangeWait) {
+ mShouldQuit = true;
+ mUsbConnectionChangeWait.notifyAll();
+ }
+ mHandlerThread.quit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ boolean release = false;
+ synchronized (mUsbConnectionChangeWait) {
+ release = !mShouldQuit;
+ }
+ if (release) {
+ release();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public void startDeviceReset(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "startDeviceReset: " + device);
+ }
+ mHandler.requestDeviceReset(device);
+ }
+
+ public void startAoap(AoapSwitchRequest request) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "startAoap: " + request.device);
+ }
+ mHandler.requestAoap(request);
+ }
+
+ private void doHandleDeviceReset(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleDeviceReset: " + device);
+ }
+ synchronized (mUsbConnectionChangeWait) {
+ mDevicesRemoved.clear();
+ mDevicesAdded.clear();
+ }
+ boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
+ UsbDevice completedDevice = null;
+ if (isInAoap) {
+ completedDevice = resetUsbDeviceAndConfirmModeChange(device);
+ } else {
+ UsbDeviceConnection conn = openConnection(device);
+ if (conn == null) {
+ throw new RuntimeException("cannot open conneciton for device: " + device);
+ } else {
+ try {
+ if (!conn.resetDevice()) {
+ throw new RuntimeException("resetDevice failed for device: " + device);
+ } else {
+ completedDevice = device;
+ }
+ } finally {
+ conn.close();
+ }
+ }
+ }
+ mListener.onDeviceResetComplete(completedDevice);
+ }
+
+ private void doHandleAoapStart(AoapSwitchRequest request) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "doHandleAoapStart: " + request.device);
+ }
+ UsbDevice device = request.device;
+ boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
+ if (isInAoap) {
+ device = resetUsbDeviceAndConfirmModeChange(device);
+ if (device == null) {
+ mListener.onAoapStartComplete(null);
+ return;
+ }
+ }
+ synchronized (mUsbConnectionChangeWait) {
+ mDevicesRemoved.clear();
+ mDevicesAdded.clear();
+ }
+ UsbDeviceConnection connection = openConnection(device);
+ try {
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER,
+ request.manufacturer);
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL,
+ request.model);
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION,
+ request.description);
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION,
+ request.version);
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, request.uri);
+ AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL,
+ request.serial);
+ AoapInterface.sendAoapStart(connection);
+ device = resetUsbDeviceAndConfirmModeChange(device);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to switch device into AOSP mode", e);
+ }
+ if (device == null) {
+ mListener.onAoapStartComplete(null);
+ connection.close();
+ return;
+ }
+ if (AoapInterface.isDeviceInAoapMode(device)) {
+ mListener.onAoapStartComplete(device);
+ } else {
+ Log.w(TAG, "Device not in AOAP mode after switching: " + device);
+ mListener.onAoapStartFailed(device);
+ }
+ connection.close();
+ }
+
+ private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "resetUsbDeviceAndConfirmModeChange: " + device);
+ }
+ int retry = 0;
+ boolean removalDetected = false;
+ while (retry < MAX_USB_DETACH_CHANGE_WAIT) {
+ UsbDeviceConnection connNow = openConnection(device);
+ if (connNow == null) {
+ removalDetected = true;
+ break;
+ }
+ // Because of the bug in UsbDeviceManager it's hard to get device out of
+ // AOAP mode in first 10s after connection. Repetitive sending of reset command helps.
+ for (int i = 0; i < USB_RESET_RETRY_COUNT; i++) {
+ connNow.resetDevice();
+ }
+ connNow.close();
+ synchronized (mUsbConnectionChangeWait) {
+ try {
+ mUsbConnectionChangeWait.wait(USB_STATE_DETACH_WAIT_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ break;
+ }
+ if (mShouldQuit) {
+ return null;
+ }
+ if (isDeviceRemovedLocked(device)) {
+ removalDetected = true;
+ break;
+ }
+ }
+ retry++;
+ }
+ if (!removalDetected) {
+ Log.w(TAG, "resetDevice failed for device, device still in the same mode: " + device);
+ return null;
+ }
+ retry = 0;
+ UsbDevice newlyAttached = null;
+ while (retry < MAX_USB_ATTACH_CHANGE_WAIT) {
+ synchronized (mUsbConnectionChangeWait) {
+ try {
+ mUsbConnectionChangeWait.wait(USB_STATE_ATTACH_WAIT_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ break;
+ }
+ if (mShouldQuit) {
+ return null;
+ }
+ newlyAttached = checkDeviceAttachedLocked(device);
+ }
+ if (newlyAttached != null) {
+ break;
+ }
+ retry++;
+ }
+ if (newlyAttached == null) {
+ Log.w(TAG, "resetDevice failed for device, device disconnected: " + device);
+ return null;
+ }
+ return newlyAttached;
+ }
+
+ private boolean isDeviceRemovedLocked(UsbDevice device) {
+ for (UsbDevice removed : mDevicesRemoved) {
+ if (UsbUtil.isDevicesMatching(device, removed)) {
+ mDevicesRemoved.clear();
+ return true;
+ }
+ }
+ mDevicesRemoved.clear();
+ return false;
+ }
+
+ private UsbDevice checkDeviceAttachedLocked(UsbDevice device) {
+ for (UsbDevice attached : mDevicesAdded) {
+ if (UsbUtil.isTheSameDevice(device, attached)) {
+ mDevicesAdded.clear();
+ return attached;
+ }
+ }
+ mDevicesAdded.clear();
+ return null;
+ }
+
+ public UsbDeviceConnection openConnection(UsbDevice device) {
+ mUsbManager.grantPermission(device);
+ return mUsbManager.openDevice(device);
+ }
+
+ private void handleUsbDeviceAttached(UsbDevice device) {
+ synchronized (mUsbConnectionChangeWait) {
+ mDevicesAdded.add(device);
+ mUsbConnectionChangeWait.notifyAll();
+ }
+ }
+
+ private void handleUsbDeviceDetached(UsbDevice device) {
+ synchronized (mUsbConnectionChangeWait) {
+ mDevicesRemoved.add(device);
+ mUsbConnectionChangeWait.notifyAll();
+ }
+ }
+
+ private class UsbStateHandler extends Handler {
+ private static final int MSG_RESET_DEVICE = 1;
+ private static final int MSG_AOAP = 2;
+
+ private UsbStateHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void requestDeviceReset(UsbDevice device) {
+ Message msg = obtainMessage(MSG_RESET_DEVICE, device);
+ sendMessage(msg);
+ }
+
+ private void requestAoap(AoapSwitchRequest request) {
+ Message msg = obtainMessage(MSG_AOAP, request);
+ sendMessage(msg);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESET_DEVICE:
+ doHandleDeviceReset((UsbDevice) msg.obj);
+ break;
+ case MSG_AOAP:
+ doHandleAoapStart((AoapSwitchRequest) msg.obj);
+ break;
+ }
+ }
+ }
+
+ private class UsbDeviceBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
+ UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ handleUsbDeviceDetached(device);
+ } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ handleUsbDeviceAttached(device);
+ }
+ }
+ }
+
+ public static class AoapSwitchRequest {
+ public final UsbDevice device;
+ public final String manufacturer;
+ public final String model;
+ public final String description;
+ public final String version;
+ public final String uri;
+ public final String serial;
+
+ AoapSwitchRequest(UsbDevice device, String manufacturer, String model,
+ String description, String version, String uri, String serial) {
+ this.device = device;
+ this.manufacturer = manufacturer;
+ this.model = model;
+ this.description = description;
+ this.version = version;
+ this.uri = uri;
+ this.serial = serial;
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java
new file mode 100644
index 0000000..b8bc68e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbHostController.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller used to handle USB device connections.
+ */
+public final class UsbHostController
+ implements UsbDeviceHandlerResolver.UsbDeviceHandlerResolverCallback {
+
+ /**
+ * Callbacks for controller
+ */
+ public interface UsbHostControllerCallbacks {
+ /** Host controller ready for shutdown*/
+ void shutdown();
+ /** Change of processing state*/
+ void processingStateChanged(boolean processing);
+ /** Title of processing changed */
+ void titleChanged(String title);
+ /** Options for USB device changed */
+ void optionsUpdated(List<UsbDeviceSettings> options);
+ }
+
+ private static final String TAG = UsbHostController.class.getSimpleName();
+ private static final boolean LOCAL_LOGD = true;
+ private static final boolean LOCAL_LOGV = true;
+
+
+ private final List<UsbDeviceSettings> mEmptyList = new ArrayList<>();
+ private final Context mContext;
+ private final UsbHostControllerCallbacks mCallback;
+ private final UsbSettingsStorage mUsbSettingsStorage;
+ private final UsbManager mUsbManager;
+ private final PackageManager mPackageManager;
+ private final UsbDeviceHandlerResolver mUsbReslover;
+ private final UsbHostControllerHandler mHandler;
+
+ private final BroadcastReceiver mUsbBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
+ UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ unsetActiveDeviceIfSerialMatch(device);
+ } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ UsbDevice device = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ setActiveDeviceIfSerialMatch(device);
+ }
+ }
+ };
+
+ @GuardedBy("this")
+ private UsbDevice mActiveDevice;
+
+ @GuardedBy("this")
+ private String mProcessingDeviceSerial;
+
+ public UsbHostController(Context context, UsbHostControllerCallbacks callbacks) {
+ mContext = context;
+ mCallback = callbacks;
+ mHandler = new UsbHostControllerHandler(Looper.myLooper());
+ mUsbSettingsStorage = new UsbSettingsStorage(context);
+ mUsbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
+ mPackageManager = context.getPackageManager();
+ mUsbReslover = new UsbDeviceHandlerResolver(mUsbManager, mContext, this);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ context.registerReceiver(mUsbBroadcastReceiver, filter);
+
+ }
+
+ private synchronized void setActiveDeviceIfSerialMatch(UsbDevice device) {
+ if (device.getSerialNumber().equals(mProcessingDeviceSerial)) {
+ mActiveDevice = device;
+ }
+ }
+
+ private synchronized void unsetActiveDeviceIfSerialMatch(UsbDevice device) {
+ mHandler.requestDeviceRemoved();
+ if (mActiveDevice != null
+ && mActiveDevice.getSerialNumber().equals(device.getSerialNumber())) {
+ mActiveDevice = null;
+ }
+ }
+
+ private synchronized boolean startDeviceProcessingIfNull(UsbDevice device) {
+ if (mActiveDevice == null) {
+ mActiveDevice = device;
+ mProcessingDeviceSerial = device.getSerialNumber();
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized void stopDeviceProcessing() {
+ mActiveDevice = null;
+ mProcessingDeviceSerial = null;
+ }
+
+ private synchronized UsbDevice getActiveDevice() {
+ return mActiveDevice;
+ }
+
+ private boolean deviceMatchedActiveDevice(UsbDevice device) {
+ UsbDevice activeDevice = getActiveDevice();
+ return activeDevice != null
+ && activeDevice.getSerialNumber().equals(device.getSerialNumber());
+ }
+
+ /**
+ * Processes device new device.
+ * <p>
+ * It will load existing settings or resolve supported handlers.
+ */
+ public void processDevice(UsbDevice device) {
+ if (!startDeviceProcessingIfNull(device)) {
+ Log.w(TAG, "Currently, other device is being processed");
+ }
+ mCallback.optionsUpdated(mEmptyList);
+ mCallback.processingStateChanged(true);
+
+ UsbDeviceSettings settings = mUsbSettingsStorage.getSettings(
+ device.getSerialNumber(), device.getVendorId(), device.getProductId());
+ if (settings != null && mUsbReslover.dispatch(
+ mActiveDevice, settings.getHandler(), settings.getAoap())) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Usb Device: " + device + " was sent to component: "
+ + settings.getHandler());
+ }
+ return;
+ }
+ mCallback.titleChanged(device.getManufacturerName() + " " + device.getProductName());
+ mUsbReslover.resolve(device);
+ }
+
+ /**
+ * Applies device settings.
+ */
+ public void applyDeviceSettings(UsbDeviceSettings settings) {
+ mUsbSettingsStorage.saveSettings(settings);
+ mUsbReslover.dispatch(getActiveDevice(), settings.getHandler(), settings.getAoap());
+ }
+
+ /**
+ * Release object.
+ */
+ public void release() {
+ mContext.unregisterReceiver(mUsbBroadcastReceiver);
+ mUsbReslover.release();
+ }
+
+ @Override
+ public void onHandlersResolveCompleted(
+ UsbDevice device, List<UsbDeviceSettings> handlers) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "onHandlersResolveComplete: " + device);
+ }
+ if (deviceMatchedActiveDevice(device)) {
+ mCallback.processingStateChanged(false);
+ if (handlers.isEmpty()) {
+ onDeviceDispatched();
+ } else {
+ mCallback.optionsUpdated(handlers);
+ }
+ } else {
+ Log.w(TAG, "Handlers ignored as they came for inactive device");
+ }
+ }
+
+ @Override
+ public void onDeviceDispatched() {
+ stopDeviceProcessing();
+ mCallback.shutdown();
+ }
+
+ void doHandleDeviceRemoved() {
+ if (getActiveDevice() == null) {
+ if (LOCAL_LOGD) {
+ Log.d(TAG, "USB device detached");
+ }
+ stopDeviceProcessing();
+ mCallback.shutdown();
+ }
+ }
+
+ private static Intent createDeviceAttachedIntent(UsbDevice device) {
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return intent;
+ }
+
+ private class UsbHostControllerHandler extends Handler {
+ private static final int MSG_DEVICE_REMOVED = 1;
+
+ private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
+
+ private UsbHostControllerHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void requestDeviceRemoved() {
+ sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DEVICE_REMOVED:
+ doHandleDeviceRemoved();
+ break;
+ default:
+ Log.w(TAG, "Unhandled message: " + msg);
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java
new file mode 100644
index 0000000..1f647c9
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbSettingsStorage.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides API to persist USB device settings.
+ */
+public final class UsbSettingsStorage {
+ private static final String TAG = UsbSettingsStorage.class.getSimpleName();
+
+ private static final String TABLE_USB_SETTINGS = "usb_devices";
+ private static final String COLUMN_SERIAL = "serial";
+ private static final String COLUMN_VID = "vid";
+ private static final String COLUMN_PID = "pid";
+ private static final String COLUMN_NAME = "name";
+ private static final String COLUMN_HANDLER = "handler";
+ private static final String COLUMN_AOAP = "aoap";
+
+ private final UsbSettingsDbHelper mDbHelper;
+
+ public UsbSettingsStorage(Context context) {
+ mDbHelper = new UsbSettingsDbHelper(context);
+ }
+
+ /**
+ * Returns settings for {@serialNumber} or null if it doesn't exist.
+ */
+ @Nullable
+ public UsbDeviceSettings getSettings(String serialNumber, int vid, int pid) {
+ try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ Cursor resultCursor = db.query(
+ TABLE_USB_SETTINGS,
+ null,
+ COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID + " = ?",
+ new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)},
+ null,
+ null,
+ null)) {
+ if (resultCursor.getCount() > 1) {
+ throw new RuntimeException("Quering for serial number: " + serialNumber
+ + " vid: " + vid + " pid: " + pid + " returned "
+ + resultCursor.getCount() + " results");
+ }
+ if (resultCursor.getCount() == 0) {
+ Log.w(TAG, "Usb setting missing for device serial: " + serialNumber
+ + " vid: " + vid + " pid: " + pid);
+ return null;
+ }
+ List<UsbDeviceSettings> settings = constructSettings(resultCursor);
+ return settings.get(0);
+ }
+ }
+
+ /**
+ * Saves or updates settings for USB device.
+ */
+ public void saveSettings(UsbDeviceSettings settings) {
+ try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+ long result = db.replace(
+ TABLE_USB_SETTINGS,
+ null,
+ settingsToContentValues(settings));
+ if (result == -1) {
+ Log.e(TAG, "Failed to save settings: " + settings);
+ }
+ }
+ }
+
+ /**
+ * Delete settings for USB device.
+ */
+ public void deleteSettings(String serialNumber, int vid, int pid) {
+ try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+ int result = db.delete(
+ TABLE_USB_SETTINGS,
+ COLUMN_SERIAL + " = ? AND " + COLUMN_VID + " = ? AND " + COLUMN_PID
+ + " = ?",
+ new String[]{serialNumber, Integer.toString(vid), Integer.toString(pid)});
+ if (result == 0) {
+ Log.w(TAG, "No settings with serialNumber: " + serialNumber
+ + " vid: " + vid + " pid: " + pid);
+ }
+ if (result > 1) {
+ Log.e(TAG, "Deleted multiple rows (" + result + ") for serialNumber: "
+ + serialNumber + " vid: " + vid + " pid: " + pid);
+ }
+ }
+ }
+
+ /**
+ * Returns all saved settings.
+ */
+ public List<UsbDeviceSettings> getAllSettings() {
+ try (SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ Cursor resultCursor = db.query(
+ TABLE_USB_SETTINGS,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)) {
+ return constructSettings(resultCursor);
+ }
+ }
+
+ private List<UsbDeviceSettings> constructSettings(Cursor cursor) {
+ if (!cursor.isBeforeFirst()) {
+ throw new RuntimeException("Cursor is not reset to before first element");
+ }
+ int serialNumberColumnId = cursor.getColumnIndex(COLUMN_SERIAL);
+ int vidColumnId = cursor.getColumnIndex(COLUMN_VID);
+ int pidColumnId = cursor.getColumnIndex(COLUMN_PID);
+ int deviceNameColumnId = cursor.getColumnIndex(COLUMN_NAME);
+ int handlerColumnId = cursor.getColumnIndex(COLUMN_HANDLER);
+ int aoapColumnId = cursor.getColumnIndex(COLUMN_AOAP);
+ List<UsbDeviceSettings> results = new ArrayList<>(cursor.getCount());
+ while (cursor.moveToNext()) {
+ results.add(UsbDeviceSettings.constructSettings(
+ cursor.getString(serialNumberColumnId),
+ cursor.getInt(vidColumnId),
+ cursor.getInt(pidColumnId),
+ cursor.getString(deviceNameColumnId),
+ ComponentName.unflattenFromString(
+ cursor.getString(handlerColumnId)),
+ cursor.getInt(aoapColumnId) != 0));
+ }
+ return results;
+ }
+
+ /**
+ * Converts {@code UsbDeviceSettings} to {@code ContentValues}.
+ */
+ public ContentValues settingsToContentValues(UsbDeviceSettings settings) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(COLUMN_SERIAL, settings.getSerialNumber());
+ contentValues.put(COLUMN_VID, settings.getVid());
+ contentValues.put(COLUMN_PID, settings.getPid());
+ contentValues.put(COLUMN_NAME, settings.getDeviceName());
+ contentValues.put(COLUMN_HANDLER, settings.getHandler().flattenToShortString());
+ contentValues.put(COLUMN_AOAP, settings.getAoap() ? 1 : 0);
+ return contentValues;
+ }
+
+
+ private static class UsbSettingsDbHelper extends SQLiteOpenHelper {
+ private static final int DATABASE_VERSION = 2;
+ private static final String DATABASE_NAME = "usb_devices.db";
+
+ UsbSettingsDbHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_USB_SETTINGS + " ("
+ + COLUMN_SERIAL + " TEXT,"
+ + COLUMN_VID + " INTEGER,"
+ + COLUMN_PID + " INTEGER,"
+ + COLUMN_NAME + " TEXT, "
+ + COLUMN_HANDLER + " TEXT,"
+ + COLUMN_AOAP + " INTEGER,"
+ + "PRIMARY KEY (" + COLUMN_SERIAL + ", " + COLUMN_VID + ", "
+ + COLUMN_PID + "))");
+ }
+
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2) {
+ db.execSQL("ALTER TABLE " + TABLE_USB_SETTINGS + " ADD " + COLUMN_AOAP
+ + " INTEGER");
+ }
+ // Do nothing at this point. Not required for v1 database.
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java
new file mode 100644
index 0000000..e27a86e
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/setting/usb/UsbUtil.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2016 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.google.android.car.kitchensink.setting.usb;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Util methods to work with USB devices.
+ */
+class UsbUtil {
+ public static final String ADB_INTERFACE_NAME = "ADB Interface";
+ public static final String AOAP_INTERFACE_NAME = "Android Accessory Interface";
+ public static final String MTP_INTERFACE_NAME = "MTP";
+
+ public static LinkedList<UsbDevice> findAllPossibleAndroidDevices(UsbManager usbManager) {
+ HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+ LinkedList<UsbDevice> androidDevices = null;
+ for (UsbDevice device : devices.values()) {
+ if (possiblyAndroid(device)) {
+ if (androidDevices == null) {
+ androidDevices = new LinkedList<>();
+ }
+ androidDevices.add(device);
+ }
+ }
+ return androidDevices;
+ }
+
+ public static boolean possiblyAndroid(UsbDevice device) {
+ int numInterfaces = device.getInterfaceCount();
+ for (int i = 0; i < numInterfaces; i++) {
+ UsbInterface usbInterface = device.getInterface(i);
+ String interfaceName = usbInterface.getName();
+ int interfaceClass = usbInterface.getInterfaceClass();
+ // more thorough check can be added, later
+ if (AOAP_INTERFACE_NAME.equals(interfaceName)
+ || ADB_INTERFACE_NAME.equals(interfaceName)
+ || MTP_INTERFACE_NAME.equals(interfaceName)
+ || interfaceClass == UsbConstants.USB_CLASS_MASS_STORAGE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isTheSameDevice(UsbDevice l, UsbDevice r) {
+ if (TextUtils.equals(l.getManufacturerName(), r.getManufacturerName())
+ && TextUtils.equals(l.getProductName(), r.getProductName())
+ && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isDevicesMatching(UsbDevice l, UsbDevice r) {
+ if (l.getVendorId() == r.getVendorId() && l.getProductId() == r.getProductId()
+ && TextUtils.equals(l.getSerialNumber(), r.getSerialNumber())) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isDeviceConnected(UsbManager usbManager, UsbDevice device) {
+ HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+ for (UsbDevice dev : devices.values()) {
+ if (isDevicesMatching(dev, device)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tests/libvehiclenetwork-native-test/VehicleNetworkAudioHelperTest.cpp b/tests/libvehiclenetwork-native-test/VehicleNetworkAudioHelperTest.cpp
index af19b36..c75a587 100644
--- a/tests/libvehiclenetwork-native-test/VehicleNetworkAudioHelperTest.cpp
+++ b/tests/libvehiclenetwork-native-test/VehicleNetworkAudioHelperTest.cpp
@@ -41,7 +41,7 @@
class VehicleHalMockForAudioFocus : public VehicleHalMock {
public:
- VehicleHalMockForAudioFocus(sp<VehicleNetwork>& vn)
+ explicit VehicleHalMockForAudioFocus(sp<VehicleNetwork>& vn)
: mVN(vn) {
mAudioProperties = new VehiclePropertiesHolder(false /* deleteConfigsInDestructor */);
vehicle_prop_config_t const * properties = getTestPropertiesForAudio();
diff --git a/tools/bootanalyze/bootanalyze.py b/tools/bootanalyze/bootanalyze.py
index 04ad14e..ad01d30 100644
--- a/tools/bootanalyze/bootanalyze.py
+++ b/tools/bootanalyze/bootanalyze.py
@@ -25,18 +25,25 @@
import os
import subprocess
import time
+import math
import datetime
import sys
import operator
TIME_DMESG = "\[\s*(\d+\.\d+)\]"
TIME_LOGCAT = "^\d\d-\d\d\s+\d\d:\d\d:\d\d\.\d\d\d"
+VALUE_TOO_BIG = 600
+MAX_RETRIES = 5
def main():
args = init_arguments()
- if args.reboot:
- reboot()
+ if args.iterate < 1:
+ raise Exception('Number of iteration must be >=1');
+
+ if args.iterate > 1 and not args.reboot:
+ print "Forcing reboot flag"
+ args.reboot = True
cfg = yaml.load(args.config)
@@ -46,13 +53,44 @@
if 'stop_event' not in cfg:
raise Exception('Logcat stop_event is missing in config');
+ data_points = {}
+ for it in range(0, args.iterate):
+ if args.iterate > 1:
+ print "Run: {0}".format(it + 1)
+ attempt = 1
+ processing_data = None
+ while attempt <= MAX_RETRIES and processing_data is None:
+ attempt += 1
+ processing_data = iterate(args, search_events, cfg)
+
+ if processing_data is None:
+ # Processing error
+ print "Failed to collect valid samples for run {0}".format(it + 1)
+ continue
+ for k, v in processing_data.iteritems():
+ if k not in data_points:
+ data_points[k] = []
+ data_points[k].append(v['value'])
+
+ if args.iterate > 1:
+ print "Avg values after {0} runs".format(args.iterate)
+ print '{0:30}: {1:<7} {2:<7}'.format("Event", "Mean", "stddev")
+
+ for item in sorted(data_points.items(), key=operator.itemgetter(1)):
+ print '{0:30}: {1:<7.5} {2:<7.5}'.format(
+ item[0], sum(item[1])/len(item[1]), stddev(item[1]))
+
+def iterate(args, search_events, cfg):
+ if args.reboot:
+ reboot()
+
logcat_events = collect_events(
search_events, 'adb logcat -b all', cfg['stop_event'])
dmesg_events = collect_events(search_events, 'adb shell dmesg')
logcat_event_time = extract_time(
- logcat_events, TIME_LOGCAT, logcat_time_func(2016));
+ logcat_events, TIME_LOGCAT, logcat_time_func(datetime.date.today().year));
logcat_original_time = extract_time(
logcat_events, TIME_LOGCAT, str);
dmesg_event_time = extract_time(
@@ -77,13 +115,26 @@
for k in events_to_correct:
if events[k] - diff_time > 0:
events[k] = events[k] - diff_time
+ if events[k] > VALUE_TOO_BIG:
+ print "Event {0} value {1} too big , possible processing error".format(
+ k, events[k])
+ return None
+
+ data_points = {}
for item in sorted(events.items(), key=operator.itemgetter(1)):
+ data_points[item[0]] = {
+ 'value': item[1],
+ 'from_dmesg': item[0] in replaced_from_dmesg,
+ 'logcat_value': logcat_original_time[item[0]]
+ }
print '{0:30}: {1:<7.5} {2:1} ({3})'.format(
item[0], item[1], '*' if item[0] in replaced_from_dmesg else '',
logcat_original_time[item[0]])
- print '\n', '* - event time was obtained from dmesg log'
+ print '\n* - event time was obtained from dmesg log\n'
+
+ return data_points
def init_arguments():
parser = argparse.ArgumentParser(description='Measures boot time.')
@@ -93,6 +144,8 @@
parser.add_argument('-c', '--config', dest='config',
default='config.yaml', type=argparse.FileType('r'),
help='config file for the tool', )
+ parser.add_argument('-n', '--iterate', dest='iterate', type=int, default=1,
+ help='number of time to repeat the measurement', )
return parser.parse_args()
def collect_events(search_events, command, stop_event=None):
@@ -144,5 +197,12 @@
def datetime_to_unix_time(ndate):
return time.mktime(ndate.timetuple()) + ndate.microsecond/1000000.0
+def stddev(data):
+ items_count = len(data)
+ avg = sum(data) / items_count
+ sq_diffs_sum = sum([(v - avg) ** 2 for v in data])
+ variance = sq_diffs_sum / items_count
+ return math.sqrt(variance)
+
if __name__ == '__main__':
main()
diff --git a/vehicle_monitor_service/Android.mk b/vehicle_monitor_service/Android.mk
new file mode 100644
index 0000000..bd9f2ac
--- /dev/null
+++ b/vehicle_monitor_service/Android.mk
@@ -0,0 +1,65 @@
+# Copyright (C) 2016 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 := $(patsubst ./%,%, $(shell cd $(LOCAL_PATH); \
+ find . -name "*.cpp" -and -not -name ".*"))
+
+LOCAL_SRC_FILES := $(filter-out main_vehiclemonitor.cpp, $(LOCAL_SRC_FILES))
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/include \
+ packages/services/Car/libvehiclemonitor/include
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder
+
+LOCAL_MODULE := libvehiclemonitorservice
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -Werror
+
+include $(BUILD_STATIC_LIBRARY)
+
+##################################
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ main_vehiclemonitor.cpp
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/include \
+ packages/services/Car/libvehiclemonitor/include
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libutils \
+ libvehiclemonitor-native
+
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+ libvehiclemonitorservice
+
+LOCAL_STRIP_MODULE := keep_symbols
+
+LOCAL_MODULE := vehicle_monitor_service
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -Werror
+
+include $(BUILD_EXECUTABLE)
diff --git a/vehicle_monitor_service/ProcessMonitor.cpp b/vehicle_monitor_service/ProcessMonitor.cpp
new file mode 100644
index 0000000..521473f
--- /dev/null
+++ b/vehicle_monitor_service/ProcessMonitor.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#define LOG_TAG "ProcessMonitor"
+
+#include <mutex>
+#include <sys/param.h>
+#include <dirent.h>
+
+#include "ProcessMonitor.h"
+
+#define DBG_VERBOSE
+#ifdef DBG_VERBOSE
+#define LOG_VERBOSE(x...) ALOGD(x)
+#else
+#define LOG_VERBOSE(x...)
+#endif
+
+#define MAX_LINE 256
+#define SELF_IO "/proc/self/io"
+#define NUM_PROC_DUMP 10
+
+namespace android {
+
+static bool procDeltaCpuCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return a.second->delta_time > b.second->delta_time;
+}
+
+static bool procDeltaMemCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return a.second->delta_rss > b.second->delta_rss;
+}
+
+static bool procDeltaWbytesCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return a.second->delta_wbytes > b.second->delta_wbytes;
+}
+
+static bool procCpuCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return (a.second->utime + a.second->utime) > (b.second->stime + b.second->stime);
+}
+
+static bool procMemCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return a.second->rss > b.second->rss;
+}
+
+static bool procWbytesCmp(const std::pair<pid_t, const std::shared_ptr<ProcInfo>> a,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>> b) {
+ return a.second->wbytes > b.second->wbytes;
+}
+
+ProcessMonitor::ProcessMonitor() {
+ //TODO: read config from policy files.
+ if (access(SELF_IO, F_OK) == -1) {
+ mIoSupported = false;
+ ALOGE("**** DISK I/O PROFILING DISABLED!!!!****\n"
+ "Kernel doesn't support I/O profiling.");
+ } else {
+ mIoSupported = true;
+ }
+}
+
+ProcessMonitor::~ProcessMonitor() {
+}
+
+void ProcessMonitor::dump(String8& msg) {
+ std::shared_lock<std::shared_timed_mutex> lock(mMutex);
+ msg.append("ProcessMonitor\n");
+ msg.appendFormat("Processes count: %d\n", (int) mProcInfoMap.size());
+ msg.append("Top CPU usage\n");
+ dumpTopProcesses(msg, procCpuCmp);
+ msg.append("Top CPU usage increase\n");
+ dumpTopProcesses(msg, procDeltaCpuCmp);
+ msg.append("Top memory usage\n");
+ dumpTopProcesses(msg, procMemCmp);
+ msg.append("Top memory usage increase\n");
+ dumpTopProcesses(msg, procDeltaMemCmp);
+ if (mIoSupported) {
+ msg.append("Top disk IO \n");
+ dumpTopProcesses(msg, procWbytesCmp);
+ msg.append("Top disk IO increase \n");
+ dumpTopProcesses(msg, procDeltaWbytesCmp);
+ } else {
+ msg.append("Disk IO monitoring not supported.\n");
+ }
+}
+
+void ProcessMonitor::dumpTopProcesses(
+ String8& msg,
+ bool (*procCmpFn) (
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>>,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>>)) {
+
+ std::vector<std::pair<pid_t, std::shared_ptr<ProcInfo>>> topPids(NUM_PROC_DUMP);
+ std::partial_sort_copy(mProcInfoMap.begin(),
+ mProcInfoMap.end(),
+ topPids.begin(),
+ topPids.end(),
+ *procCmpFn);
+ for (auto it = topPids.begin(); it != topPids.end(); ++it) {
+ msg.appendFormat("(%s) PID: %d: delta_time: %" PRIu64 ", delta_rss: %" PRIu64 ", "
+ "delta_wbytes: %" PRIu64 ", utime: %" PRIu64" , stime: %" PRIu64 ", "
+ "rss: %" PRIu64 ", wbytes: %" PRIu64 "\n",
+ it->second->name.c_str(),
+ it->first,
+ it->second->delta_time,
+ it->second->delta_rss,
+ it->second->delta_wbytes,
+ it->second->utime,
+ it->second->stime,
+ it->second->rss,
+ it->second->wbytes);
+ }
+
+}
+
+status_t ProcessMonitor::setAppPriority(uint32_t , uint32_t, uint32_t) {
+ std::unique_lock<std::shared_timed_mutex> lock(mMutex);
+ // TODO implement.
+ return NO_ERROR;
+}
+
+status_t ProcessMonitor::process() {
+ status_t status = updateProcessInfo();
+ if (status != NO_ERROR) {
+ return status;
+ }
+ return improveSystemHealth();
+}
+
+status_t ProcessMonitor::improveSystemHealth() {
+ // TODO: implement policy enforcer. kill apps that abuse system.
+ return NO_ERROR;
+}
+
+status_t ProcessMonitor::updateProcessInfo() {
+ std::unique_lock<std::shared_timed_mutex> lock(mMutex);
+ std::set<pid_t> oldPids;
+ populateExistingPids(oldPids);
+ DIR *procDir;
+ procDir = opendir("/proc");
+ if (!procDir) {
+ ALOGE("Failed to open /proc dir");
+ return PERMISSION_DENIED;
+ }
+ struct dirent *pidDir;
+ pid_t pid;
+ while ((pidDir = readdir(procDir))) {
+ if (!isdigit(pidDir->d_name[0])) {
+ continue;
+ }
+ pid = atoi(pidDir->d_name);
+ updateOrAddProcess(pid);
+ oldPids.erase(pid);
+ }
+ deleteOutdatedPids(oldPids);
+ return NO_ERROR;
+}
+
+void ProcessMonitor::deleteOutdatedPids(std::set<pid_t>& pidSet) {
+ for(auto it = pidSet.begin(); it != pidSet.end(); ++it) {
+ LOG_VERBOSE("Process %d ended. Removing from process map", *it);
+ mProcInfoMap.erase(*it);
+ }
+}
+
+void ProcessMonitor::populateExistingPids(std::set<pid_t>& pidSet) {
+ for(auto it = mProcInfoMap.begin(); it != mProcInfoMap.end(); ++it) {
+ pidSet.insert(it->first);
+ }
+}
+
+void ProcessMonitor::updateOrAddProcess(pid_t pid) {
+ auto pidDataIt = mProcInfoMap.find(pid);
+ std::shared_ptr<ProcInfo> pidData;
+ if (pidDataIt == mProcInfoMap.end()) {
+ pidData = std::make_shared<ProcInfo>();
+ mProcInfoMap.insert(std::pair<pid_t, std::shared_ptr<ProcInfo>>(pid, pidData));
+ } else {
+ pidData = pidDataIt->second;
+ }
+ auto originalPidData = std::make_shared<ProcInfo>(*pidData);
+ readStat(pidData, pid);
+ if (mIoSupported) {
+ readIo(pidData, pid);
+ }
+ readCmdline(pidData, pid);
+ readStatus(pidData, pid);
+ updateDiffs(pidData, originalPidData);
+}
+
+void ProcessMonitor::readStat(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
+ char filename[64];
+ sprintf(filename, "/proc/%d/stat", pid);
+ FILE *file;
+ file = fopen(filename, "r");
+ if (!file) {
+ ALOGD("Failed to open file %s for reading", filename);
+ return;
+ }
+ fscanf(file,
+ "%*d %*s %*c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
+ "%" SCNu64
+ "%" SCNu64 "%*d %*d %*d %*d %*d %*d %*d "
+ "%*" SCNu64
+ "%" SCNu64 "%*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d "
+ "%*d",
+ &pidData->utime,
+ &pidData->stime,
+ &pidData->rss);
+ fclose(file);
+}
+
+void ProcessMonitor::readIo(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
+ char filename[64];
+ sprintf(filename, "/proc/%d/io", pid);
+ FILE *file;
+ file = fopen(filename, "r");
+ if (!file) {
+ ALOGD("Failed to open file %s for reading", filename);
+ return;
+ }
+ char buf[MAX_LINE];
+ while (fgets(buf, MAX_LINE, file)) {
+ sscanf(buf, "write_bytes: %" PRIu64, &pidData->wbytes);
+ }
+ fclose(file);
+}
+
+void ProcessMonitor::readCmdline(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
+ char filename[64];
+ sprintf(filename, "/proc/%d/cmdline", pid);
+ FILE *file;
+ file = fopen(filename, "r");
+ if (!file) {
+ ALOGD("Failed to open file %s for reading", filename);
+ return;
+ }
+ char buf[MAXPATHLEN];
+ fgets(buf, MAXPATHLEN, file);
+ fclose(file);
+ if (strlen(buf) > 0) {
+ pidData->name.assign(buf);
+ }
+}
+
+void ProcessMonitor::readStatus(std::shared_ptr<ProcInfo> pidData, pid_t pid) {
+ char filename[64];
+ sprintf(filename, "/proc/%d/status", pid);
+ FILE *file;
+ file = fopen(filename, "r");
+ if (!file) {
+ ALOGD("Failed to open file %s for reading", filename);
+ return;
+ }
+ char line[MAX_LINE];
+ unsigned int uid;
+ while (fgets(line, MAX_LINE, file)) {
+ sscanf(line, "Uid: %u", &uid);
+ }
+ fclose(file);
+ pidData->uid = uid;
+
+}
+
+void ProcessMonitor::updateDiffs(std::shared_ptr<ProcInfo> pidData,
+ std::shared_ptr<ProcInfo> oldPidData) {
+ pidData->delta_utime = pidData->utime - oldPidData->utime;
+ pidData->delta_stime = pidData->stime - oldPidData->stime;
+ pidData->delta_time = pidData->delta_utime + pidData->delta_stime;
+ pidData->delta_rss = pidData->rss - oldPidData->rss;
+ pidData->delta_wbytes = pidData->wbytes - oldPidData->wbytes;
+}
+
+}; // namespace android
diff --git a/vehicle_monitor_service/ProcessMonitor.h b/vehicle_monitor_service/ProcessMonitor.h
new file mode 100644
index 0000000..d9f4d25
--- /dev/null
+++ b/vehicle_monitor_service/ProcessMonitor.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#ifndef CAR_PROCESS_MONITOR_H_
+#define CAR_PROCESS_MONITOR_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <shared_mutex>
+
+#include <inttypes.h>
+#include <cutils/compiler.h>
+
+#include <binder/BinderService.h>
+#include <binder/IBinder.h>
+#include <utils/String8.h>
+
+#include <IVehicleMonitor.h>
+#include <HandlerThread.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+struct ProcInfo {
+ pid_t pid;
+ uid_t uid;
+ std::string name;
+ uint64_t utime;
+ uint64_t stime;
+ uint64_t rss;
+ uint64_t wbytes;
+ int64_t delta_utime;
+ int64_t delta_stime;
+ int64_t delta_time;
+ int64_t delta_rss;
+ int64_t delta_wbytes;
+};
+
+// ----------------------------------------------------------------------------
+
+// This class is used to collect information about running processes.
+// It also enforces certain policies to running apps - ex. kills non-system,
+// non-foreground applications that use too much memory, CPU, or write too much
+// data to disk.
+class ProcessMonitor {
+public:
+ ProcessMonitor();
+ ~ProcessMonitor();
+
+ void dump(String8& msg);
+ status_t setAppPriority(uint32_t pid, uint32_t uid, uint32_t priority);
+ status_t process();
+
+private:
+ status_t updateProcessInfo();
+ void populateExistingPids(std::set<pid_t>& pidSet);
+ void deleteOutdatedPids(std::set<pid_t>& pidSet);
+ void updateOrAddProcess(pid_t pid);
+ void readStat(std::shared_ptr<ProcInfo> pidData, pid_t pid);
+ void readIo(std::shared_ptr<ProcInfo> pidData, pid_t pid);
+ void readCmdline(std::shared_ptr<ProcInfo> pidData, pid_t pid);
+ void readStatus(std::shared_ptr<ProcInfo> pidData, pid_t pid);
+ void updateDiffs(std::shared_ptr<ProcInfo> pidData,
+ std::shared_ptr<ProcInfo> oldPidData);
+ status_t improveSystemHealth();
+ void dumpTopProcesses(String8& msg,
+ bool (*procCmpFn) (
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>>,
+ const std::pair<pid_t, const std::shared_ptr<ProcInfo>>));
+
+
+private:
+ std::map<pid_t, std::shared_ptr<ProcInfo>> mProcInfoMap;
+ bool mIoSupported;
+ mutable std::shared_timed_mutex mMutex;
+};
+
+}
+
+#endif /* CAR_PROCESS_MONITOR_H_ */
diff --git a/vehicle_monitor_service/VehicleMonitorService.cpp b/vehicle_monitor_service/VehicleMonitorService.cpp
new file mode 100644
index 0000000..1ff251a
--- /dev/null
+++ b/vehicle_monitor_service/VehicleMonitorService.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#define LOG_TAG "VehicleMonitor"
+
+#include <assert.h>
+
+#include <binder/PermissionCache.h>
+#include <utils/Errors.h>
+#include <utils/SystemClock.h>
+
+#include "VehicleMonitorService.h"
+
+//#define DBG_VERBOSE
+#ifdef DBG_VERBOSE
+#define LOG_VERBOSE(x...) ALOGD(x)
+#else
+#define LOG_VERBOSE(x...)
+#endif
+
+namespace android {
+
+const nsecs_t monitorInterval = 15000000000; // 15s
+
+VehicleMonitorMessageHandler::VehicleMonitorMessageHandler(const sp<Looper>& looper,
+ VehicleMonitorService& service)
+ : mLooper(looper),
+ mService(service),
+ mLastDispatchTime(0) {
+ mLooper->sendMessageDelayed(monitorInterval, this, Message(COLLECT_DATA));
+}
+
+VehicleMonitorMessageHandler::~VehicleMonitorMessageHandler() {
+}
+
+void VehicleMonitorMessageHandler::dump(String8& msg) {
+ msg.appendFormat("mLastDispatchTime:%" PRId64 "\n", mLastDispatchTime);
+ mProcessMonitor.dump(msg);
+}
+
+void VehicleMonitorMessageHandler::doHandleCollectData() {
+ {
+ std::lock_guard<std::mutex> autoLock(mLock);
+ mLastDispatchTime = elapsedRealtime();
+ mProcessMonitor.process();
+ }
+
+ // TODO: do better timing for sendMessage
+ mLooper->sendMessageDelayed(monitorInterval, this, Message(COLLECT_DATA));
+}
+
+void VehicleMonitorMessageHandler::handleMessage(const Message& message) {
+ switch (message.what) {
+ case COLLECT_DATA:
+ doHandleCollectData();
+ break;
+ default:
+ // TODO?
+ break;
+ }
+}
+
+// ----------------------------------------------------
+VehicleMonitorService* VehicleMonitorService::sInstance = NULL;
+
+status_t VehicleMonitorService::dump(int fd, const Vector<String16>& /*args*/) {
+ static const String16 sDump("android.permission.DUMP");
+ String8 msg;
+ if (!PermissionCache::checkCallingPermission(sDump)) {
+ msg.appendFormat("Permission Denial: "
+ "can't dump VNS from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, msg.string(), msg.size());
+ return NO_ERROR;
+ }
+ msg.appendFormat("*Handler, now in ms:%" PRId64 "\n", elapsedRealtime());
+ mHandler->dump(msg);
+ write(fd, msg.string(), msg.size());
+ return NO_ERROR;
+}
+
+VehicleMonitorService::VehicleMonitorService() {
+ sInstance = this;
+}
+
+VehicleMonitorService::~VehicleMonitorService() {
+ sInstance = NULL;
+}
+
+void VehicleMonitorService::binderDied(const wp<IBinder>& who) {
+ std::lock_guard<std::mutex> autoLock(mLock);
+ sp<IBinder> ibinder = who.promote();
+ ibinder->unlinkToDeath(this);
+ // TODO: reset all priorities set by CarService.
+}
+
+void VehicleMonitorService::release() {
+ std::lock_guard<std::mutex> autoLock(mLock);
+ mHandlerThread->quit();
+}
+
+void VehicleMonitorService::onFirstRef() {
+ std::lock_guard<std::mutex> autoLock(mLock);
+ mHandlerThread = new HandlerThread();
+ status_t r = mHandlerThread->start("VMS.NATIVE_LOOP");
+ if (r != NO_ERROR) {
+ ALOGE("cannot start handler thread, error:%d", r);
+ return;
+ }
+ sp<VehicleMonitorMessageHandler> handler(
+ new VehicleMonitorMessageHandler(mHandlerThread->getLooper(), *this));
+ assert(handler.get() != NULL);
+ mHandler = handler;
+}
+
+status_t VehicleMonitorService::setAppPriority(uint32_t, uint32_t, vehicle_app_priority) {
+ //TODO
+ return NO_ERROR;
+}
+
+status_t VehicleMonitorService::setMonitorListener(
+ const sp<IVehicleMonitorListener> &listener) {
+ sp<IBinder> ibinder = IInterface::asBinder(listener);
+ LOG_VERBOSE("setMonitorListener, binder 0x%x", ibinder.get());
+ //TODO
+ return NO_ERROR;
+}
+
+}; // namespace android
diff --git a/vehicle_monitor_service/VehicleMonitorService.h b/vehicle_monitor_service/VehicleMonitorService.h
new file mode 100644
index 0000000..57ade9d
--- /dev/null
+++ b/vehicle_monitor_service/VehicleMonitorService.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#ifndef CAR_VEHICLE_MONITOR_SERVICE_H_
+#define CAR_VEHICLE_MONITOR_SERVICE_H_
+
+#include <mutex>
+#include <inttypes.h>
+#include <cutils/compiler.h>
+
+#include <binder/BinderService.h>
+#include <binder/IBinder.h>
+#include <utils/String8.h>
+
+#include <ProcessMonitor.h>
+#include <IVehicleMonitor.h>
+#include <IVehicleMonitorListener.h>
+#include <HandlerThread.h>
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class VehicleMonitorService;
+
+/**
+ * MessageHandler to handle periodic data processing.
+ * Init / release is handled in the handler thread to allow upper layer to
+ * allocate resource for the thread.
+ */
+class VehicleMonitorMessageHandler : public MessageHandler {
+ enum {
+ COLLECT_DATA = 0,
+ };
+
+public:
+ // not passing VMS as sp as this is held by VMS always.
+ VehicleMonitorMessageHandler(
+ const sp<Looper>& mLooper, VehicleMonitorService& service);
+ virtual ~VehicleMonitorMessageHandler();
+
+ void dump(String8& msg);
+
+private:
+ void handleMessage(const Message& message);
+ void doHandleCollectData();
+
+private:
+ mutable std::mutex mLock;
+ const sp<Looper> mLooper;
+ ProcessMonitor mProcessMonitor;
+ VehicleMonitorService& mService;
+ int64_t mLastDispatchTime;
+};
+
+// ----------------------------------------------------------------------------
+class VehicleMonitorService :
+ public BinderService<VehicleMonitorService>,
+ public BnVehicleMonitor,
+ public IBinder::DeathRecipient {
+public:
+ static const char* getServiceName() ANDROID_API {
+ return IVehicleMonitor::SERVICE_NAME;
+ };
+
+ VehicleMonitorService();
+ ~VehicleMonitorService();
+ virtual status_t dump(int fd, const Vector<String16>& args);
+ void release();
+ virtual void binderDied(const wp<IBinder>& who);
+ virtual status_t setAppPriority(
+ uint32_t pid, uint32_t uid, vehicle_app_priority priority);
+ virtual status_t setMonitorListener(
+ const sp<IVehicleMonitorListener> &listener);
+
+private:
+ // RefBase
+ virtual void onFirstRef();
+private:
+ static VehicleMonitorService* sInstance;
+ sp<HandlerThread> mHandlerThread;
+ sp<VehicleMonitorMessageHandler> mHandler;
+ mutable std::mutex mLock;
+};
+
+}
+
+#endif /* CAR_VEHICLE_MONITOR_SERVICE_H_ */
diff --git a/vehicle_monitor_service/main_vehiclemonitor.cpp b/vehicle_monitor_service/main_vehiclemonitor.cpp
new file mode 100644
index 0000000..2994a07
--- /dev/null
+++ b/vehicle_monitor_service/main_vehiclemonitor.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "VMS"
+
+#include <signal.h>
+
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+
+#include "VehicleMonitorService.h"
+
+using namespace android;
+
+int main(int /*argc*/, char** /*argv*/)
+{
+ ALOGI("vehicle_monitor_service: starting");
+ signal(SIGPIPE, SIG_IGN);
+ // this will wait for service manager if not available yet.
+ sp<IServiceManager> sm = defaultServiceManager();
+ VehicleMonitorService::instantiate();
+ ProcessState::self()->startThreadPool();
+ IPCThreadState::self()->joinThreadPool();
+}
diff --git a/vehicle_network_service/Android.mk b/vehicle_network_service/Android.mk
index bf828db..a0a135f 100644
--- a/vehicle_network_service/Android.mk
+++ b/vehicle_network_service/Android.mk
@@ -23,12 +23,14 @@
LOCAL_SRC_FILES := $(filter-out main_vehiclenetwork.cpp, $(LOCAL_SRC_FILES))
LOCAL_C_INCLUDES += \
- libcore/include \
frameworks/base/include \
packages/services/Car/libvehiclenetwork/include \
external/libxml2/include \
external/icu/icu4c/source/common
+LOCAL_SHARED_LIBRARIES := \
+ libbinder
+
LOCAL_MODULE := libvehiclenetworkservice
LOCAL_MODULE_TAGS := optional
@@ -43,7 +45,6 @@
main_vehiclenetwork.cpp
LOCAL_C_INCLUDES += \
- libcore/include \
frameworks/base/include \
packages/services/Car/libvehiclenetwork/include \
external/libxml2/include \