Merge "Avoid JUnit4 collision in android-support-test, mockito-target"
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 2fd6740..eb07c05 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -6,7 +6,9 @@
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test apct-perftests-utils
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ apct-perftests-utils
LOCAL_PACKAGE_NAME := CorePerfTests
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index ecb8d95..815bc80 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.core.frameworks.perftests">
+ package="android.core.perftests">
<application>
<uses-library android:name="android.test.runner" />
@@ -8,6 +8,6 @@
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.core.frameworks.perftests"/>
+ android:targetPackage="android.core.perftests"/>
</manifest>
diff --git a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
index d444c92..a8d1f26 100644
--- a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
@@ -27,7 +27,7 @@
import android.view.View;
import android.view.ViewGroup;
-import com.android.core.frameworks.perftests.R;
+import android.core.perftests.R;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/graphics/Android.mk b/apct-tests/perftests/graphics/Android.mk
index afdd743..cba35f3 100644
--- a/apct-tests/perftests/graphics/Android.mk
+++ b/apct-tests/perftests/graphics/Android.mk
@@ -3,11 +3,13 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_JAVA_LIBRARIES := apct-perftests-utils android-support-test
-
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ apct-perftests-utils
+
LOCAL_PACKAGE_NAME := GraphicsPerfTests
include $(BUILD_PACKAGE)
diff --git a/apct-tests/perftests/graphics/AndroidManifest.xml b/apct-tests/perftests/graphics/AndroidManifest.xml
index 5416458..6c8bc07 100644
--- a/apct-tests/perftests/graphics/AndroidManifest.xml
+++ b/apct-tests/perftests/graphics/AndroidManifest.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.frameworks.perftests">
+ package="android.graphics.perftests">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -16,6 +16,6 @@
</application>
<instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.frameworks.perftests"/>
+ android:targetPackage="android.graphics.perftests"/>
</manifest>
diff --git a/apct-tests/perftests/graphics/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/graphics/src/android/graphics/perftests/RenderNodePerfTest.java
new file mode 100644
index 0000000..19047d3
--- /dev/null
+++ b/apct-tests/perftests/graphics/src/android/graphics/perftests/RenderNodePerfTest.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.graphics.perftests;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class RenderNodePerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testMeasureRenderNodeJniOverhead() {
+ RenderNode node = RenderNode.create("benchmark", null);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ while (state.keepRunning()) {
+ node.setTranslationX(1.0f);
+ }
+ }
+}
diff --git a/apct-tests/perftests/graphics/src/android/graphics/perftests/VectorDrawablePerfTest.java b/apct-tests/perftests/graphics/src/android/graphics/perftests/VectorDrawablePerfTest.java
index 2af3b04..a15be2e 100644
--- a/apct-tests/perftests/graphics/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/graphics/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -30,7 +30,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
-import com.android.frameworks.perftests.R;
+import android.graphics.perftests.R;
import org.junit.Rule;
import org.junit.Test;
diff --git a/apct-tests/perftests/misc/Android.mk b/apct-tests/perftests/misc/Android.mk
new file mode 100644
index 0000000..db5d643
--- /dev/null
+++ b/apct-tests/perftests/misc/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ apct-perftests-utils
+
+LOCAL_PACKAGE_NAME := MiscPerfTests
+
+include $(BUILD_PACKAGE)
+
diff --git a/apct-tests/perftests/misc/AndroidManifest.xml b/apct-tests/perftests/misc/AndroidManifest.xml
new file mode 100644
index 0000000..9d9d5f4
--- /dev/null
+++ b/apct-tests/perftests/misc/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.misc.perftests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="android.perftests.utils.StubActivity" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.misc.perftests"/>
+
+</manifest>
diff --git a/apct-tests/perftests/misc/src/android/util/perftests/LogPerfTest.java b/apct-tests/perftests/misc/src/android/util/perftests/LogPerfTest.java
new file mode 100644
index 0000000..07cd33f
--- /dev/null
+++ b/apct-tests/perftests/misc/src/android/util/perftests/LogPerfTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.util.perftests;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class LogPerfTest {
+
+ private final String[] strings = new String[] {
+ "This is a test log string 1",
+ "This is a test log string 2",
+ "This is a test log string 3",
+ "This is a test log string 4",
+ "This is a test log string 5",
+ };
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testLogPerf() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ int i = 0;
+ while (state.keepRunning()) {
+ Log.d("LogPerfTest", strings[(i++) % strings.length]);
+ }
+ }
+}
diff --git a/apct-tests/perftests/misc/src/java/lang/perftests/SystemPerfTest.java b/apct-tests/perftests/misc/src/java/lang/perftests/SystemPerfTest.java
new file mode 100644
index 0000000..6a49c03
--- /dev/null
+++ b/apct-tests/perftests/misc/src/java/lang/perftests/SystemPerfTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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 java.lang.perftests;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SystemPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testNanoTimePerf() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ System.nanoTime();
+ }
+ }
+}
diff --git a/api/system-current.txt b/api/system-current.txt
index 3e47ac7..3197322 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -15944,6 +15944,7 @@
method public java.lang.String getSerial();
method public boolean releaseInterface(android.hardware.usb.UsbInterface);
method public android.hardware.usb.UsbRequest requestWait();
+ method public boolean resetDevice();
method public boolean setConfiguration(android.hardware.usb.UsbConfiguration);
method public boolean setInterface(android.hardware.usb.UsbInterface);
}
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 6e4c9de..fafe116 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -17,6 +17,7 @@
package android.hardware.usb;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbPort;
@@ -116,4 +117,7 @@
/* Sets the port's current role. */
void setPortRoles(in String portId, int powerRole, int dataRole);
+
+ /* Sets USB device connection handler. */
+ void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler);
}
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index c062b3a..54fea52 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -16,6 +16,7 @@
package android.hardware.usb;
+import android.annotation.SystemApi;
import android.os.ParcelFileDescriptor;
import java.io.FileDescriptor;
@@ -215,8 +216,20 @@
}
/**
+ * Reset USB port for the connected device.
+ *
+ * @return true if reset succeeds.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean resetDevice() {
+ return native_reset_device();
+ }
+
+ /**
* Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation
- * Note that this may return requests queued on multiple
+ * Note that this may return requests queued on multiple
* {@link android.hardware.usb.UsbEndpoint}s.
* When multiple endpoints are in use, {@link android.hardware.usb.UsbRequest#getEndpoint} and
* {@link android.hardware.usb.UsbRequest#getClientData} can be useful in determining
@@ -263,4 +276,5 @@
int offset, int length, int timeout);
private native UsbRequest native_request_wait();
private native String native_get_serial();
+ private native boolean native_reset_device();
}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 629db06..df4785e 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -19,7 +19,9 @@
import com.android.internal.util.Preconditions;
+import android.annotation.Nullable;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
@@ -469,8 +471,20 @@
* {@hide}
*/
public void grantPermission(UsbDevice device) {
+ grantPermission(device, Process.myUid());
+ }
+
+ /**
+ * Grants permission for USB device to given uid without showing system dialog.
+ * Only system components can call this function.
+ * @param device to request permissions for
+ * @uid uid to give permission
+ *
+ * {@hide}
+ */
+ public void grantPermission(UsbDevice device, int uid) {
try {
- mService.grantDevicePermission(device, Process.myUid());
+ mService.grantDevicePermission(device, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -488,11 +502,9 @@
try {
int uid = mContext.getPackageManager()
.getPackageUidAsUser(packageName, mContext.getUserId());
- mService.grantDevicePermission(device, uid);
+ grantPermission(device, uid);
} catch (NameNotFoundException e) {
Log.e(TAG, "Package " + packageName + " not found.", e);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
}
}
@@ -631,6 +643,26 @@
}
}
+ /**
+ * Sets the component that will handle USB device connection.
+ * <p>
+ * Setting component allows to specify external USB host manager to handle use cases, where
+ * selection dialog for an activity that will handle USB device is undesirable.
+ * Only system components can call this function, as it requires the MANAGE_USB permission.
+ *
+ * @param usbDeviceConnectionHandler The component to handle usb connections,
+ * {@code null} to unset.
+ *
+ * {@hide}
+ */
+ public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
+ try {
+ mService.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public static String addFunction(String functions, String function) {
if (USB_FUNCTION_NONE.equals(functions)) {
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index fe7cdcc..52579e5 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -44,6 +44,7 @@
}
public native final void writeInterfaceToken(String interfaceName);
+ public native final void writeBool(boolean val);
public native final void writeInt8(byte val);
public native final void writeInt16(short val);
public native final void writeInt32(int val);
@@ -52,6 +53,8 @@
public native final void writeDouble(double val);
public native final void writeString(String val);
+ public native final void writeBoolArray(int size, boolean[] val);
+ public native final void writeBoolVector(boolean[] val);
public native final void writeInt8Array(int size, byte[] val);
public native final void writeInt8Vector(byte[] val);
public native final void writeInt16Array(int size, short[] val);
@@ -70,6 +73,7 @@
public native final void writeStrongBinder(IHwBinder binder);
public native final void enforceInterface(String interfaceName);
+ public native final boolean readBool();
public native final byte readInt8();
public native final short readInt16();
public native final int readInt32();
@@ -78,6 +82,8 @@
public native final double readDouble();
public native final String readString();
+ public native final boolean[] readBoolArray(int size);
+ public native final boolean[] readBoolVector();
public native final byte[] readInt8Array(int size);
public native final byte[] readInt8Vector();
public native final short[] readInt16Array(int size);
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index bce5ec1..51d818b 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -24,6 +24,10 @@
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
@@ -128,6 +132,12 @@
*/
public class RenderNode {
+ // Use a Holder to allow static initialization in the boot image.
+ private static class NoImagePreloadHolder {
+ public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ RenderNode.class.getClassLoader(), nGetNativeFinalizer(), 1024);
+ }
+
private boolean mValid;
// Do not access directly unless you are ThreadedRenderer
final long mNativeRenderNode;
@@ -135,6 +145,7 @@
private RenderNode(String name, View owningView) {
mNativeRenderNode = nCreate(name);
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
mOwningView = owningView;
if (mOwningView instanceof SurfaceView) {
nRequestPositionUpdates(mNativeRenderNode, (SurfaceView) mOwningView);
@@ -145,6 +156,7 @@
* @see RenderNode#adopt(long)
*/
private RenderNode(long nativePtr) {
+ NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativePtr);
mNativeRenderNode = nativePtr;
mOwningView = null;
}
@@ -812,99 +824,143 @@
// Intentionally not static because it acquires a reference to 'this'
private native long nCreate(String name);
- private static native void nDestroyRenderNode(long renderNode);
+ private static native long nGetNativeFinalizer();
private static native void nSetDisplayList(long renderNode, long newData);
-
- // Matrix
-
- private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
- private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
- private static native boolean nHasIdentityMatrix(long renderNode);
-
- // Properties
-
- private static native boolean nOffsetTopAndBottom(long renderNode, int offset);
- private static native boolean nOffsetLeftAndRight(long renderNode, int offset);
- private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
- int right, int bottom);
- private static native boolean nSetBottom(long renderNode, int bottom);
- private static native boolean nSetRight(long renderNode, int right);
- private static native boolean nSetTop(long renderNode, int top);
- private static native boolean nSetLeft(long renderNode, int left);
- private static native boolean nSetCameraDistance(long renderNode, float distance);
- private static native boolean nSetPivotY(long renderNode, float pivotY);
- private static native boolean nSetPivotX(long renderNode, float pivotX);
- private static native boolean nSetLayerType(long renderNode, int layerType);
- private static native boolean nSetLayerPaint(long renderNode, long paint);
- private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
- private static native boolean nSetClipBounds(long renderNode, int left, int top,
- int right, int bottom);
- private static native boolean nSetClipBoundsEmpty(long renderNode);
- private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
- private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
- private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
- int right, int bottom, float radius, float alpha);
- private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath,
- float alpha);
- private static native boolean nSetOutlineEmpty(long renderNode);
- private static native boolean nSetOutlineNone(long renderNode);
- private static native boolean nHasShadow(long renderNode);
- private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
- private static native boolean nSetRevealClip(long renderNode,
- boolean shouldClip, float x, float y, float radius);
- private static native boolean nSetAlpha(long renderNode, float alpha);
- private static native boolean nSetHasOverlappingRendering(long renderNode,
- boolean hasOverlappingRendering);
- private static native boolean nSetElevation(long renderNode, float lift);
- private static native boolean nSetTranslationX(long renderNode, float translationX);
- private static native boolean nSetTranslationY(long renderNode, float translationY);
- private static native boolean nSetTranslationZ(long renderNode, float translationZ);
- private static native boolean nSetRotation(long renderNode, float rotation);
- private static native boolean nSetRotationX(long renderNode, float rotationX);
- private static native boolean nSetRotationY(long renderNode, float rotationY);
- private static native boolean nSetScaleX(long renderNode, float scaleX);
- private static native boolean nSetScaleY(long renderNode, float scaleY);
- private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
- private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
-
- private static native boolean nHasOverlappingRendering(long renderNode);
- private static native boolean nGetClipToOutline(long renderNode);
- private static native float nGetAlpha(long renderNode);
- private static native float nGetCameraDistance(long renderNode);
- private static native float nGetScaleX(long renderNode);
- private static native float nGetScaleY(long renderNode);
- private static native float nGetElevation(long renderNode);
- private static native float nGetTranslationX(long renderNode);
- private static native float nGetTranslationY(long renderNode);
- private static native float nGetTranslationZ(long renderNode);
- private static native float nGetRotation(long renderNode);
- private static native float nGetRotationX(long renderNode);
- private static native float nGetRotationY(long renderNode);
- private static native boolean nIsPivotExplicitlySet(long renderNode);
- private static native float nGetPivotX(long renderNode);
- private static native float nGetPivotY(long renderNode);
private static native void nOutput(long renderNode);
private static native int nGetDebugSize(long renderNode);
-
private static native void nRequestPositionUpdates(long renderNode, SurfaceView callback);
- ///////////////////////////////////////////////////////////////////////////
// Animations
- ///////////////////////////////////////////////////////////////////////////
private static native void nAddAnimator(long renderNode, long animatorPtr);
private static native void nEndAllAnimators(long renderNode);
///////////////////////////////////////////////////////////////////////////
- // Finalization
+ // Fast native methods
///////////////////////////////////////////////////////////////////////////
- @Override
- protected void finalize() throws Throwable {
- try {
- nDestroyRenderNode(mNativeRenderNode);
- } finally {
- super.finalize();
- }
- }
+ // Matrix
+
+ @FastNative
+ private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
+ @FastNative
+ private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
+ @FastNative
+ private static native boolean nHasIdentityMatrix(long renderNode);
+
+ // Properties
+
+ @FastNative
+ private static native boolean nOffsetTopAndBottom(long renderNode, int offset);
+ @FastNative
+ private static native boolean nOffsetLeftAndRight(long renderNode, int offset);
+ @FastNative
+ private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top,
+ int right, int bottom);
+ @FastNative
+ private static native boolean nSetBottom(long renderNode, int bottom);
+ @FastNative
+ private static native boolean nSetRight(long renderNode, int right);
+ @FastNative
+ private static native boolean nSetTop(long renderNode, int top);
+ @FastNative
+ private static native boolean nSetLeft(long renderNode, int left);
+ @FastNative
+ private static native boolean nSetCameraDistance(long renderNode, float distance);
+ @FastNative
+ private static native boolean nSetPivotY(long renderNode, float pivotY);
+ @FastNative
+ private static native boolean nSetPivotX(long renderNode, float pivotX);
+ @FastNative
+ private static native boolean nSetLayerType(long renderNode, int layerType);
+ @FastNative
+ private static native boolean nSetLayerPaint(long renderNode, long paint);
+ @FastNative
+ private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+ @FastNative
+ private static native boolean nSetClipBounds(long renderNode, int left, int top,
+ int right, int bottom);
+ @FastNative
+ private static native boolean nSetClipBoundsEmpty(long renderNode);
+ @FastNative
+ private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
+ @FastNative
+ private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+ @FastNative
+ private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top,
+ int right, int bottom, float radius, float alpha);
+ @FastNative
+ private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath,
+ float alpha);
+ @FastNative
+ private static native boolean nSetOutlineEmpty(long renderNode);
+ @FastNative
+ private static native boolean nSetOutlineNone(long renderNode);
+ @FastNative
+ private static native boolean nHasShadow(long renderNode);
+ @FastNative
+ private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
+ @FastNative
+ private static native boolean nSetRevealClip(long renderNode,
+ boolean shouldClip, float x, float y, float radius);
+ @FastNative
+ private static native boolean nSetAlpha(long renderNode, float alpha);
+ @FastNative
+ private static native boolean nSetHasOverlappingRendering(long renderNode,
+ boolean hasOverlappingRendering);
+ @FastNative
+ private static native boolean nSetElevation(long renderNode, float lift);
+ @FastNative
+ private static native boolean nSetTranslationX(long renderNode, float translationX);
+ @FastNative
+ private static native boolean nSetTranslationY(long renderNode, float translationY);
+ @FastNative
+ private static native boolean nSetTranslationZ(long renderNode, float translationZ);
+ @FastNative
+ private static native boolean nSetRotation(long renderNode, float rotation);
+ @FastNative
+ private static native boolean nSetRotationX(long renderNode, float rotationX);
+ @FastNative
+ private static native boolean nSetRotationY(long renderNode, float rotationY);
+ @FastNative
+ private static native boolean nSetScaleX(long renderNode, float scaleX);
+ @FastNative
+ private static native boolean nSetScaleY(long renderNode, float scaleY);
+ @FastNative
+ private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
+ @FastNative
+ private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
+
+ @FastNative
+ private static native boolean nHasOverlappingRendering(long renderNode);
+ @FastNative
+ private static native boolean nGetClipToOutline(long renderNode);
+ @FastNative
+ private static native float nGetAlpha(long renderNode);
+ @FastNative
+ private static native float nGetCameraDistance(long renderNode);
+ @FastNative
+ private static native float nGetScaleX(long renderNode);
+ @FastNative
+ private static native float nGetScaleY(long renderNode);
+ @FastNative
+ private static native float nGetElevation(long renderNode);
+ @FastNative
+ private static native float nGetTranslationX(long renderNode);
+ @FastNative
+ private static native float nGetTranslationY(long renderNode);
+ @FastNative
+ private static native float nGetTranslationZ(long renderNode);
+ @FastNative
+ private static native float nGetRotation(long renderNode);
+ @FastNative
+ private static native float nGetRotationX(long renderNode);
+ @FastNative
+ private static native float nGetRotationY(long renderNode);
+ @FastNative
+ private static native boolean nIsPivotExplicitlySet(long renderNode);
+ @FastNative
+ private static native float nGetPivotX(long renderNode);
+ @FastNative
+ private static native float nGetPivotY(long renderNode);
}
diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp
index 1ba9fc5..f899c00 100644
--- a/core/jni/android_hardware_UsbDeviceConnection.cpp
+++ b/core/jni/android_hardware_UsbDeviceConnection.cpp
@@ -246,6 +246,18 @@
return result;
}
+static jboolean
+android_hardware_UsbDeviceConnection_reset_device(JNIEnv *env, jobject thiz)
+{
+ struct usb_device* device = get_device_from_object(env, thiz);
+ if (!device) {
+ ALOGE("device is closed in native_reset_device");
+ return JNI_FALSE;
+ }
+ int ret = usb_device_reset(device);
+ return (ret == 0) ? JNI_TRUE : JNI_FALSE;
+}
+
static const JNINativeMethod method_table[] = {
{"native_open", "(Ljava/lang/String;Ljava/io/FileDescriptor;)Z",
(void *)android_hardware_UsbDeviceConnection_open},
@@ -264,6 +276,7 @@
(void *)android_hardware_UsbDeviceConnection_request_wait},
{ "native_get_serial", "()Ljava/lang/String;",
(void*)android_hardware_UsbDeviceConnection_get_serial },
+ {"native_reset_device","()Z", (void *)android_hardware_UsbDeviceConnection_reset_device},
};
int register_android_hardware_UsbDeviceConnection(JNIEnv *env)
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 0202303..94918f6 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -333,6 +333,7 @@
return val; \
}
+DEFINE_PARCEL_WRITER(Bool,jboolean)
DEFINE_PARCEL_WRITER(Int8,jbyte)
DEFINE_PARCEL_WRITER(Int16,jshort)
DEFINE_PARCEL_WRITER(Int32,jint)
@@ -347,6 +348,17 @@
DEFINE_PARCEL_READER(Float,jfloat)
DEFINE_PARCEL_READER(Double,jdouble)
+static jboolean JHwParcel_native_readBool(JNIEnv *env, jobject thiz) {
+ hardware::Parcel *parcel =
+ JHwParcel::GetNativeContext(env, thiz)->getParcel();
+
+ bool val;
+ status_t err = parcel->readBool(&val);
+ signalExceptionForError(env, err);
+
+ return (jboolean)val;
+}
+
static void JHwParcel_native_writeStatus(
JNIEnv *env, jobject thiz, jint statusCode) {
using hardware::Status;
@@ -489,6 +501,90 @@
DEFINE_PARCEL_VECTOR_WRITER(Float,jfloat)
DEFINE_PARCEL_VECTOR_WRITER(Double,jdouble)
+static void JHwParcel_native_writeBoolArray(
+ JNIEnv *env, jobject thiz, jint size, jbooleanArray valObj) {
+ if (valObj == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
+ return;
+ }
+
+ jsize len = env->GetArrayLength(valObj);
+
+ if (len != size) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz);
+
+ jboolean *src = env->GetBooleanArrayElements(valObj, nullptr);
+
+ bool *dst =
+ (bool *)impl->getStorage()->allocTemporaryStorage(size * sizeof(bool));
+
+ for (jint i = 0; i < size; ++i) {
+ dst[i] = src[i];
+ }
+
+ env->ReleaseBooleanArrayElements(valObj, src, 0 /* mode */);
+ src = nullptr;
+
+ hardware::Parcel *parcel = impl->getParcel();
+
+ size_t parentHandle;
+ status_t err = parcel->writeBuffer(
+ dst, size * sizeof(*dst), &parentHandle);
+
+ signalExceptionForError(env, err);
+}
+
+static void JHwParcel_native_writeBoolVector(
+ JNIEnv *env, jobject thiz, jbooleanArray valObj) {
+ if (valObj == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
+ return;
+ }
+
+ sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz);
+
+ hidl_vec<bool> *vec =
+ (hidl_vec<bool> *)impl->getStorage()->allocTemporaryStorage(
+ sizeof(hidl_vec<bool>));
+
+ jsize len = env->GetArrayLength(valObj);
+
+ jboolean *src = env->GetBooleanArrayElements(valObj, nullptr);
+
+ bool *dst =
+ (bool *)impl->getStorage()->allocTemporaryStorage(len * sizeof(bool));
+
+ for (jsize i = 0; i < len; ++i) {
+ dst[i] = src[i];
+ }
+
+ env->ReleaseBooleanArrayElements(valObj, src, 0 /* mode */);
+ src = nullptr;
+
+ vec->setToExternal(dst, len);
+
+ hardware::Parcel *parcel = impl->getParcel();
+
+ size_t parentHandle;
+ status_t err = parcel->writeBuffer(vec, sizeof(*vec), &parentHandle);
+
+ if (err == OK) {
+ size_t childHandle;
+
+ err = vec->writeEmbeddedToParcel(
+ parcel,
+ parentHandle,
+ 0 /* parentOffset */,
+ &childHandle);
+ }
+
+ signalExceptionForError(env, err);
+}
+
static void JHwParcel_native_writeStrongBinder(
JNIEnv *env, jobject thiz, jobject binderObj) {
sp<hardware::IBinder> binder;
@@ -616,6 +712,64 @@
DEFINE_PARCEL_VECTOR_READER(Float,jfloat,Float)
DEFINE_PARCEL_VECTOR_READER(Double,jdouble,Double)
+static jbooleanArray JHwParcel_native_readBoolArray(
+ JNIEnv *env, jobject thiz, jint size) {
+ hardware::Parcel *parcel =
+ JHwParcel::GetNativeContext(env, thiz)->getParcel();
+
+ size_t parentHandle;
+ const bool *val = static_cast<const bool *>(
+ parcel->readBuffer(&parentHandle));
+
+ jbooleanArray valObj = env->NewBooleanArray(size);
+
+ for (jint i = 0; i < size; ++i) {
+ jboolean x = val[i];
+ env->SetBooleanArrayRegion(valObj, i, 1, &x);
+ }
+
+ return valObj;
+}
+
+static jbooleanArray JHwParcel_native_readBoolVector(
+ JNIEnv *env, jobject thiz) {
+ hardware::Parcel *parcel =
+ JHwParcel::GetNativeContext(env, thiz)->getParcel();
+
+ size_t parentHandle;
+
+ const hidl_vec<bool> *vec =
+ (const hidl_vec<bool> *)parcel->readBuffer(&parentHandle);
+
+ if (vec == NULL) {
+ signalExceptionForError(env, UNKNOWN_ERROR);
+ return NULL;
+ }
+
+ size_t childHandle;
+
+ status_t err = const_cast<hidl_vec<bool> *>(vec)
+ ->readEmbeddedFromParcel(
+ *parcel,
+ parentHandle,
+ 0 /* parentOffset */,
+ &childHandle);
+
+ if (err != OK) {
+ signalExceptionForError(env, err);
+ return NULL;
+ }
+
+ jbooleanArray valObj = env->NewBooleanArray(vec->size());
+
+ for (size_t i = 0; i < vec->size(); ++i) {
+ jboolean x = (*vec)[i];
+ env->SetBooleanArrayRegion(valObj, i, 1, &x);
+ }
+
+ return valObj;
+}
+
static jobjectArray MakeStringArray(
JNIEnv *env, const hidl_string *array, size_t size) {
ScopedLocalRef<jclass> stringKlass(
@@ -825,6 +979,7 @@
{ "writeInterfaceToken", "(Ljava/lang/String;)V",
(void *)JHwParcel_native_writeInterfaceToken },
+ { "writeBool", "(Z)V", (void *)JHwParcel_native_writeBool },
{ "writeInt8", "(B)V", (void *)JHwParcel_native_writeInt8 },
{ "writeInt16", "(S)V", (void *)JHwParcel_native_writeInt16 },
{ "writeInt32", "(I)V", (void *)JHwParcel_native_writeInt32 },
@@ -835,6 +990,8 @@
{ "writeString", "(Ljava/lang/String;)V",
(void *)JHwParcel_native_writeString },
+ { "writeBoolArray", "(I[Z)V", (void *)JHwParcel_native_writeBoolArray },
+ { "writeBoolVector", "([Z)V", (void *)JHwParcel_native_writeBoolVector },
{ "writeInt8Array", "(I[B)V", (void *)JHwParcel_native_writeInt8Array },
{ "writeInt8Vector", "([B)V", (void *)JHwParcel_native_writeInt8Vector },
{ "writeInt16Array", "(I[S)V", (void *)JHwParcel_native_writeInt16Array },
@@ -862,6 +1019,7 @@
{ "enforceInterface", "(Ljava/lang/String;)V",
(void *)JHwParcel_native_enforceInterface },
+ { "readBool", "()Z", (void *)JHwParcel_native_readBool },
{ "readInt8", "()B", (void *)JHwParcel_native_readInt8 },
{ "readInt16", "()S", (void *)JHwParcel_native_readInt16 },
{ "readInt32", "()I", (void *)JHwParcel_native_readInt32 },
@@ -872,6 +1030,8 @@
{ "readString", "()Ljava/lang/String;",
(void *)JHwParcel_native_readString },
+ { "readBoolArray", "(I)[Z", (void *)JHwParcel_native_readBoolArray },
+ { "readBoolVector", "()[Z", (void *)JHwParcel_native_readBoolVector },
{ "readInt8Array", "(I)[B", (void *)JHwParcel_native_readInt8Array },
{ "readInt8Vector", "()[B", (void *)JHwParcel_native_readInt8Vector },
{ "readInt16Array", "(I)[S", (void *)JHwParcel_native_readInt16Array },
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index b0028e1..9a91d8e 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -120,12 +120,15 @@
return reinterpret_cast<jlong>(renderNode);
}
-static void android_view_RenderNode_destroyRenderNode(JNIEnv* env,
- jobject clazz, jlong renderNodePtr) {
- RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+static void releaseRenderNode(RenderNode* renderNode) {
renderNode->decStrong(0);
}
+static jlong android_view_RenderNode_getNativeFinalizer(JNIEnv* env,
+ jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&releaseRenderNode));
+}
+
static void android_view_RenderNode_setDisplayList(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jlong displayListPtr) {
class RemovedObserver : public TreeObserver {
@@ -643,79 +646,83 @@
const char* const kClassPathName = "android/view/RenderNode";
static const JNINativeMethod gMethods[] = {
+// ----------------------------------------------------------------------------
+// Regular JNI
+// ----------------------------------------------------------------------------
{ "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
- { "nDestroyRenderNode", "(J)V", (void*) android_view_RenderNode_destroyRenderNode },
+ { "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer },
{ "nSetDisplayList", "(JJ)V", (void*) android_view_RenderNode_setDisplayList },
{ "nOutput", "(J)V", (void*) android_view_RenderNode_output },
{ "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize },
-
- { "nSetLayerType", "!(JI)Z", (void*) android_view_RenderNode_setLayerType },
- { "nSetLayerPaint", "!(JJ)Z", (void*) android_view_RenderNode_setLayerPaint },
- { "nSetStaticMatrix", "!(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix },
- { "nSetAnimationMatrix", "!(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix },
- { "nSetClipToBounds", "!(JZ)Z", (void*) android_view_RenderNode_setClipToBounds },
- { "nSetClipBounds", "!(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
- { "nSetClipBoundsEmpty", "!(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty },
- { "nSetProjectBackwards", "!(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards },
- { "nSetProjectionReceiver","!(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver },
-
- { "nSetOutlineRoundRect", "!(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
- { "nSetOutlineConvexPath", "!(JJF)Z", (void*) android_view_RenderNode_setOutlineConvexPath },
- { "nSetOutlineEmpty", "!(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
- { "nSetOutlineNone", "!(J)Z", (void*) android_view_RenderNode_setOutlineNone },
- { "nHasShadow", "!(J)Z", (void*) android_view_RenderNode_hasShadow },
- { "nSetClipToOutline", "!(JZ)Z", (void*) android_view_RenderNode_setClipToOutline },
- { "nSetRevealClip", "!(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
-
- { "nSetAlpha", "!(JF)Z", (void*) android_view_RenderNode_setAlpha },
- { "nSetHasOverlappingRendering", "!(JZ)Z",
- (void*) android_view_RenderNode_setHasOverlappingRendering },
- { "nSetElevation", "!(JF)Z", (void*) android_view_RenderNode_setElevation },
- { "nSetTranslationX", "!(JF)Z", (void*) android_view_RenderNode_setTranslationX },
- { "nSetTranslationY", "!(JF)Z", (void*) android_view_RenderNode_setTranslationY },
- { "nSetTranslationZ", "!(JF)Z", (void*) android_view_RenderNode_setTranslationZ },
- { "nSetRotation", "!(JF)Z", (void*) android_view_RenderNode_setRotation },
- { "nSetRotationX", "!(JF)Z", (void*) android_view_RenderNode_setRotationX },
- { "nSetRotationY", "!(JF)Z", (void*) android_view_RenderNode_setRotationY },
- { "nSetScaleX", "!(JF)Z", (void*) android_view_RenderNode_setScaleX },
- { "nSetScaleY", "!(JF)Z", (void*) android_view_RenderNode_setScaleY },
- { "nSetPivotX", "!(JF)Z", (void*) android_view_RenderNode_setPivotX },
- { "nSetPivotY", "!(JF)Z", (void*) android_view_RenderNode_setPivotY },
- { "nSetCameraDistance", "!(JF)Z", (void*) android_view_RenderNode_setCameraDistance },
- { "nSetLeft", "!(JI)Z", (void*) android_view_RenderNode_setLeft },
- { "nSetTop", "!(JI)Z", (void*) android_view_RenderNode_setTop },
- { "nSetRight", "!(JI)Z", (void*) android_view_RenderNode_setRight },
- { "nSetBottom", "!(JI)Z", (void*) android_view_RenderNode_setBottom },
- { "nSetLeftTopRightBottom","!(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
- { "nOffsetLeftAndRight", "!(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight },
- { "nOffsetTopAndBottom", "!(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom },
-
- { "nHasOverlappingRendering", "!(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering },
- { "nGetClipToOutline", "!(J)Z", (void*) android_view_RenderNode_getClipToOutline },
- { "nGetAlpha", "!(J)F", (void*) android_view_RenderNode_getAlpha },
- { "nGetCameraDistance", "!(J)F", (void*) android_view_RenderNode_getCameraDistance },
- { "nGetScaleX", "!(J)F", (void*) android_view_RenderNode_getScaleX },
- { "nGetScaleY", "!(J)F", (void*) android_view_RenderNode_getScaleY },
- { "nGetElevation", "!(J)F", (void*) android_view_RenderNode_getElevation },
- { "nGetTranslationX", "!(J)F", (void*) android_view_RenderNode_getTranslationX },
- { "nGetTranslationY", "!(J)F", (void*) android_view_RenderNode_getTranslationY },
- { "nGetTranslationZ", "!(J)F", (void*) android_view_RenderNode_getTranslationZ },
- { "nGetRotation", "!(J)F", (void*) android_view_RenderNode_getRotation },
- { "nGetRotationX", "!(J)F", (void*) android_view_RenderNode_getRotationX },
- { "nGetRotationY", "!(J)F", (void*) android_view_RenderNode_getRotationY },
- { "nIsPivotExplicitlySet", "!(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet },
- { "nHasIdentityMatrix", "!(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix },
-
- { "nGetTransformMatrix", "!(JJ)V", (void*) android_view_RenderNode_getTransformMatrix },
- { "nGetInverseTransformMatrix","!(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix },
-
- { "nGetPivotX", "!(J)F", (void*) android_view_RenderNode_getPivotX },
- { "nGetPivotY", "!(J)F", (void*) android_view_RenderNode_getPivotY },
-
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
-
{ "nRequestPositionUpdates", "(JLandroid/view/SurfaceView;)V", (void*) android_view_RenderNode_requestPositionUpdates },
+
+// ----------------------------------------------------------------------------
+// Fast JNI via @FastNative annotation in RenderNode.java
+// ----------------------------------------------------------------------------
+ { "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType },
+ { "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint },
+ { "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix },
+ { "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix },
+ { "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds },
+ { "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
+ { "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty },
+ { "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards },
+ { "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver },
+
+ { "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
+ { "nSetOutlineConvexPath", "(JJF)Z", (void*) android_view_RenderNode_setOutlineConvexPath },
+ { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty },
+ { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone },
+ { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow },
+ { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline },
+ { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
+
+ { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha },
+ { "nSetHasOverlappingRendering", "(JZ)Z",
+ (void*) android_view_RenderNode_setHasOverlappingRendering },
+ { "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation },
+ { "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX },
+ { "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY },
+ { "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ },
+ { "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation },
+ { "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX },
+ { "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY },
+ { "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX },
+ { "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY },
+ { "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX },
+ { "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY },
+ { "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance },
+ { "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft },
+ { "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop },
+ { "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight },
+ { "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom },
+ { "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
+ { "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight },
+ { "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom },
+
+ { "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering },
+ { "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline },
+ { "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha },
+ { "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance },
+ { "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX },
+ { "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY },
+ { "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation },
+ { "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX },
+ { "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY },
+ { "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ },
+ { "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation },
+ { "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX },
+ { "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY },
+ { "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet },
+ { "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix },
+
+ { "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix },
+ { "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix },
+
+ { "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX },
+ { "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
};
int register_android_view_RenderNode(JNIEnv* env) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 40ffb74..70ea4df 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3339,6 +3339,14 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
+ android:permission="android.permission.UPDATE_CONFIG">
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_CT_LOGS" />
+ <data android:scheme="content" android:host="*" android:mimeType="*/*" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.MasterClearReceiver"
android:permission="android.permission.MASTER_CLEAR">
<intent-filter
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ec93e47..15b32c75 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1829,6 +1829,11 @@
Do not set this to true for production devices. Doing so will cause you to fail CTS. -->
<bool name="config_disableUsbPermissionDialogs">false</bool>
+ <!-- Activity to handle Usb Device connection in USB Host side. Keeping it to null value will
+ lead into handling it inside system using Intent resolution. Non-null contents will have
+ format of package-name/ActivityClassName. -->
+ <string name="config_UsbDeviceConnectionHandling_component" translatable="false">@null</string>
+
<!-- Minimum span needed to begin a touch scaling gesture.
If the span is equal to or greater than this size, a scaling gesture
will begin, where supported. (See android.view.ScaleGestureDetector)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 166862f..e219b76 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1866,6 +1866,7 @@
<java-symbol type="string" name="usb_ptp_notification_title" />
<java-symbol type="string" name="usb_midi_notification_title" />
<java-symbol type="string" name="usb_supplying_notification_title" />
+ <java-symbol type="string" name="config_UsbDeviceConnectionHandling_component" />
<java-symbol type="string" name="vpn_text" />
<java-symbol type="string" name="vpn_text_long" />
<java-symbol type="string" name="vpn_title" />
diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
new file mode 100644
index 0000000..ec65f8d
--- /dev/null
+++ b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
@@ -0,0 +1,181 @@
+/*
+ * 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.server.updates;
+
+import com.android.internal.util.HexDump;
+import android.os.FileUtils;
+import android.system.Os;
+import android.system.ErrnoException;
+import android.util.Base64;
+import android.util.Slog;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringBufferInputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.NoSuchAlgorithmException;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ private static final String TAG = "CTLogInstallReceiver";
+ private static final String LOGDIR_PREFIX = "logs-";
+
+ public CertificateTransparencyLogInstallReceiver() {
+ super("/data/misc/keychain/trusted_ct_logs/", "ct_logs", "metadata/", "version");
+ }
+
+ @Override
+ protected void install(byte[] content, int version) throws IOException {
+ /* Install is complicated here because we translate the input, which is a JSON file
+ * containing log information to a directory with a file per log. To support atomically
+ * replacing the old configuration directory with the new there's a bunch of steps. We
+ * create a new directory with the logs and then do an atomic update of the current symlink
+ * to point to the new directory.
+ */
+
+ // 1. Ensure that the update dir exists and is readable
+ updateDir.mkdir();
+ if (!updateDir.isDirectory()) {
+ throw new IOException("Unable to make directory " + updateDir.getCanonicalPath());
+ }
+ if (!updateDir.setReadable(true, false)) {
+ throw new IOException("Unable to set permissions on " +
+ updateDir.getCanonicalPath());
+ }
+ File currentSymlink = new File(updateDir, "current");
+ File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version));
+ File oldDirectory;
+ // 2. Handle the corner case where the new directory already exists.
+ if (newVersion.exists()) {
+ // If the symlink has already been updated then the update died between steps 7 and 8
+ // and so we cannot delete the directory since its in use. Instead just bump the version
+ // and return.
+ if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) {
+ writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes());
+ deleteOldLogDirectories();
+ return;
+ } else {
+ FileUtils.deleteContentsAndDir(newVersion);
+ }
+ }
+ try {
+ // 3. Create /data/misc/keychain/trusted_ct_logs/<new_version>/ .
+ newVersion.mkdir();
+ if (!newVersion.isDirectory()) {
+ throw new IOException("Unable to make directory " + newVersion.getCanonicalPath());
+ }
+ if (!newVersion.setReadable(true, false)) {
+ throw new IOException("Failed to set " +newVersion.getCanonicalPath() +
+ " readable");
+ }
+
+ // 4. For each log in the log file create the corresponding file in <new_version>/ .
+ try {
+ JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8));
+ JSONArray logs = json.getJSONArray("logs");
+ for (int i = 0; i < logs.length(); i++) {
+ JSONObject log = logs.getJSONObject(i);
+ installLog(newVersion, log);
+ }
+ } catch (JSONException e) {
+ throw new IOException("Failed to parse logs", e);
+ }
+
+ // 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic
+ // update.
+ File tempSymlink = new File(updateDir, "new_symlink");
+ try {
+ Os.symlink(newVersion.getCanonicalPath(), tempSymlink.getCanonicalPath());
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to create symlink", e);
+ }
+
+ // 6. Update the symlink target, this is the actual update step.
+ tempSymlink.renameTo(currentSymlink.getAbsoluteFile());
+ } catch (IOException | RuntimeException e) {
+ FileUtils.deleteContentsAndDir(newVersion);
+ throw e;
+ }
+ Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath());
+ // 7. Update the current version information
+ writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes());
+ // 8. Cleanup
+ deleteOldLogDirectories();
+ }
+
+ private void installLog(File directory, JSONObject logObject) throws IOException {
+ try {
+ String logFilename = getLogFileName(logObject.getString("key"));
+ File file = new File(directory, logFilename);
+ try (OutputStreamWriter out =
+ new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
+ writeLogEntry(out, "key", logObject.getString("key"));
+ writeLogEntry(out, "url", logObject.getString("url"));
+ writeLogEntry(out, "description", logObject.getString("description"));
+ }
+ if (!file.setReadable(true, false)) {
+ throw new IOException("Failed to set permissions on " + file.getCanonicalPath());
+ }
+ } catch (JSONException e) {
+ throw new IOException("Failed to parse log", e);
+ }
+
+ }
+
+ /**
+ * Get the filename for a log based on its public key. This must be kept in sync with
+ * org.conscrypt.ct.CTLogStoreImpl.
+ */
+ private String getLogFileName(String base64PublicKey) {
+ byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT);
+ try {
+ byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes);
+ return HexDump.toHexString(id, false);
+ } catch (NoSuchAlgorithmException e) {
+ // SHA-256 is guaranteed to be available.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void writeLogEntry(OutputStreamWriter out, String key, String value)
+ throws IOException {
+ out.write(key + ":" + value + "\n");
+ }
+
+ private void deleteOldLogDirectories() throws IOException {
+ if (!updateDir.exists()) {
+ return;
+ }
+ File currentTarget = new File(updateDir, "current").getCanonicalFile();
+ FileFilter filter = new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX);
+ }
+ };
+ for (File f : updateDir.listFiles(filter)) {
+ FileUtils.deleteContentsAndDir(f);
+ }
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b28843a..39e522e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -227,6 +227,7 @@
/**
* System property whose value is either "true" or "false", indicating whether
+ * device owner is present.
*/
private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.device_owner";
@@ -4558,14 +4559,14 @@
}
}
- private void wipeDataLocked(boolean wipeExtRequested, String reason) {
+ private void wipeDataLocked(boolean wipeExtRequested, String reason, boolean force) {
if (wipeExtRequested) {
StorageManager sm = (StorageManager) mContext.getSystemService(
Context.STORAGE_SERVICE);
sm.wipeAdoptableDisks();
}
try {
- RecoverySystem.rebootWipeUserData(mContext, reason);
+ RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force);
} catch (IOException | SecurityException e) {
Slog.w(LOG_TAG, "Failed requesting data wipe", e);
}
@@ -4600,17 +4601,35 @@
}
}
boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0;
+ // If the admin is the only one who has set the restriction: force wipe, even if
+ // {@link UserManager.DISALLOW_FACTORY_RESET} is set. Reason is that the admin
+ // could remove this user restriction anyway.
+ boolean force = (userHandle == UserHandle.USER_SYSTEM)
+ && isAdminOnlyOneWhoSetRestriction(admin,
+ UserManager.DISALLOW_FACTORY_RESET, UserHandle.USER_SYSTEM);
wipeDeviceOrUserLocked(wipeExtRequested, userHandle,
- "DevicePolicyManager.wipeData() from " + source);
+ "DevicePolicyManager.wipeData() from " + source, force);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
}
}
- private void wipeDeviceOrUserLocked(boolean wipeExtRequested, final int userHandle, String reason) {
+ private boolean isAdminOnlyOneWhoSetRestriction(ActiveAdmin admin, String userRestriction,
+ int userId) {
+ int source = mUserManager.getUserRestrictionSource(userRestriction, UserHandle.of(userId));
+ if (isDeviceOwner(admin.info.getComponent(), userId)) {
+ return source == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER;
+ } else if (isProfileOwner(admin.info.getComponent(), userId)) {
+ return source == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
+ }
+ return false;
+ }
+
+ private void wipeDeviceOrUserLocked(boolean wipeExtRequested, final int userHandle,
+ String reason, boolean force) {
if (userHandle == UserHandle.USER_SYSTEM) {
- wipeDataLocked(wipeExtRequested, reason);
+ wipeDataLocked(wipeExtRequested, reason, force);
} else {
mHandler.post(new Runnable() {
@Override
@@ -4786,7 +4805,7 @@
if (wipeData) {
// Call without holding lock.
wipeDeviceOrUserLocked(false, identifier,
- "reportFailedPasswordAttempt()");
+ "reportFailedPasswordAttempt()", false);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 965341e..1d850e1 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -16,6 +16,8 @@
package com.android.server.usb;
+import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
@@ -24,6 +26,7 @@
import android.hardware.usb.UsbInterface;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -63,11 +66,20 @@
@GuardedBy("mLock")
private UsbSettingsManager mCurrentSettings;
+ @GuardedBy("mLock")
+ private ComponentName mUsbDeviceConnectionHandler;
+
public UsbHostManager(Context context, UsbAlsaManager alsaManager) {
mContext = context;
mHostBlacklist = context.getResources().getStringArray(
com.android.internal.R.array.config_usbHostBlacklist);
mUsbAlsaManager = alsaManager;
+ String deviceConnectionHandler = context.getResources().getString(
+ com.android.internal.R.string.config_UsbDeviceConnectionHandling_component);
+ if (!TextUtils.isEmpty(deviceConnectionHandler)) {
+ setUsbDeviceConnectionHandler(ComponentName.unflattenFromString(
+ deviceConnectionHandler));
+ }
}
public void setCurrentSettings(UsbSettingsManager settings) {
@@ -82,6 +94,18 @@
}
}
+ public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
+ synchronized (mLock) {
+ mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
+ }
+ }
+
+ private @Nullable ComponentName getUsbDeviceConnectionHandler() {
+ synchronized (mLock) {
+ return mUsbDeviceConnectionHandler;
+ }
+ }
+
private boolean isBlackListed(String deviceName) {
int count = mHostBlacklist.length;
for (int i = 0; i < count; i++) {
@@ -219,10 +243,17 @@
synchronized (mLock) {
if (mNewDevice != null) {
mNewDevice.setConfigurations(
- mNewConfigurations.toArray(new UsbConfiguration[mNewConfigurations.size()]));
+ mNewConfigurations.toArray(
+ new UsbConfiguration[mNewConfigurations.size()]));
mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
Slog.d(TAG, "Added device " + mNewDevice);
- getCurrentSettings().deviceAttached(mNewDevice);
+ ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
+ if (usbDeviceConnectionHandler == null) {
+ getCurrentSettings().deviceAttached(mNewDevice);
+ } else {
+ getCurrentSettings().deviceAttachedForFixedHandler(mNewDevice,
+ usbDeviceConnectionHandler);
+ }
mUsbAlsaManager.usbDeviceAdded(mNewDevice);
} else {
Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
@@ -292,6 +323,9 @@
for (String name : mDevices.keySet()) {
pw.println(" " + name + ": " + mDevices.get(name));
}
+ if (mUsbDeviceConnectionHandler != null) {
+ pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
+ }
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d6dbe90..81ac2dd 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -19,6 +19,7 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -385,6 +386,14 @@
}
@Override
+ public void setUsbDeviceConnectionHandler(ComponentName usbDeviceConnectionHandler) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+ if (mHostManager != null) {
+ mHostManager.setUsbDeviceConnectionHandler(usbDeviceConnectionHandler);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index de9ede3..6b9acf2 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -760,6 +760,31 @@
resolveActivity(intent, matches, defaultPackage, device, null);
}
+ public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
+ final Intent intent = createDeviceAttachedIntent(device);
+
+ // Send broadcast to running activity with registered intent
+ mUserContext.sendBroadcast(intent);
+
+ ApplicationInfo appInfo;
+ try {
+ appInfo = mPackageManager.getApplicationInfo(component.getPackageName(), 0);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Default USB handling package not found: " + component.getPackageName());
+ return;
+ }
+
+ grantDevicePermission(device, appInfo.uid);
+
+ Intent activityIntent = new Intent(intent);
+ activityIntent.setComponent(component);
+ try {
+ mUserContext.startActivityAsUser(activityIntent, mUser);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "unable to start activity " + activityIntent);
+ }
+ }
+
public void deviceDetached(UsbDevice device) {
// clear temporary permissions for the device
mDevicePermissionMap.remove(device.getDeviceName());
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk b/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk
new file mode 100644
index 0000000..3137a73
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/Android.mk
@@ -0,0 +1,35 @@
+# 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_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := AoapTestDeviceApp
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.xml
new file mode 100644
index 0000000..99bb520
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.hardware.usb.aoapdevicetest" >
+ <application android:label="UsbAoapDeviceTestApp" >
+ <activity android:name=".UsbAoapDeviceTestActivity"
+ android:configChanges="keyboard|keyboardHidden" >
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
+ </intent-filter>
+ <meta-data
+ android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
+ android:resource="@xml/accessory_filter"/>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml
new file mode 100644
index 0000000..cc71cf9
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/layout/device.xml
@@ -0,0 +1,20 @@
+<?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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+</LinearLayout>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.xml b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.xml
new file mode 100644
index 0000000..d854a45
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/res/xml/accessory_filter.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>
+ <usb-accessory model="AOAP Test App" manufacturer="Android"/>
+</resources>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java b/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java
new file mode 100644
index 0000000..aa4f8ca
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestDevice/src/com/android/hardware/usb/aoapdevicetest/UsbAoapDeviceTestActivity.java
@@ -0,0 +1,183 @@
+/*
+ * 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.hardware.usb.aoapdevicetest;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+
+public class UsbAoapDeviceTestActivity extends Activity {
+ private static final String TAG = UsbAoapDeviceTestActivity.class.getSimpleName();
+ private static final boolean DBG = true;
+
+ private static final String ACTION_USB_ACCESSORY_PERMISSION =
+ "com.android.hardware.usb.aoapdevicetest.ACTION_USB_ACCESSORY_PERMISSION";
+
+ private UsbManager mUsbManager;
+ private AccessoryReceiver mReceiver;
+ private ParcelFileDescriptor mFd;
+ private ReaderThread mReaderThread;
+ private UsbAccessory mAccessory;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.device);
+
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+ filter.addAction(ACTION_USB_ACCESSORY_PERMISSION);
+ mReceiver = new AccessoryReceiver();
+ registerReceiver(mReceiver, filter);
+
+ Intent intent = getIntent();
+ if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ UsbAccessory accessory =
+ (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ onAccessoryAttached(accessory);
+ } else {
+ throw new RuntimeException("USB accessory is null.");
+ }
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ IoUtils.closeQuietly(mFd);
+ if (mReaderThread != null) {
+ mReaderThread.requestToQuit();
+ try {
+ mReaderThread.join(1000);
+ } catch (InterruptedException e) {
+ }
+ if (mReaderThread.isAlive()) { // reader thread stuck
+ Log.w(TAG, "ReaderThread still alive");
+ }
+ }
+ }
+
+ private void onAccessoryAttached(UsbAccessory accessory) {
+ Log.i(TAG, "Starting AOAP discovery protocol, accessory attached: " + accessory);
+ // Check whether we have permission to access the accessory.
+ if (!mUsbManager.hasPermission(accessory)) {
+ Log.i(TAG, "Prompting the user for access to the accessory.");
+ Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION);
+ intent.setPackage(getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ mUsbManager.requestPermission(accessory, pendingIntent);
+ return;
+ }
+ mFd = mUsbManager.openAccessory(accessory);
+ if (mFd == null) {
+ Log.e(TAG, "UsbManager.openAccessory returned null");
+ finish();
+ return;
+ }
+ mAccessory = accessory;
+ mReaderThread = new ReaderThread(mFd);
+ mReaderThread.start();
+ }
+
+ private void onAccessoryDetached(UsbAccessory accessory) {
+ Log.i(TAG, "Accessory detached: " + accessory);
+ finish();
+ }
+
+ private class ReaderThread extends Thread {
+ private boolean mShouldQuit = false;
+ private final FileInputStream mInputStream;
+ private final FileOutputStream mOutputStream;
+ private final byte[] mBuffer = new byte[16384];
+
+ private ReaderThread(ParcelFileDescriptor fd) {
+ super("AOAP");
+ mInputStream = new FileInputStream(fd.getFileDescriptor());
+ mOutputStream = new FileOutputStream(fd.getFileDescriptor());
+ }
+
+ private synchronized void requestToQuit() {
+ mShouldQuit = true;
+ }
+
+ private synchronized boolean shouldQuit() {
+ return mShouldQuit;
+ }
+
+ @Override
+ public void run() {
+ while (!shouldQuit()) {
+ try {
+ int read = mInputStream.read(mBuffer);
+ } catch (IOException e) {
+ Log.i(TAG, "ReaderThread IOException", e);
+ // AOAP App should release FD when IOException happens.
+ // If FD is kept, device will not behave nicely on reset and multiple reset
+ // can be required.
+ finish();
+ return;
+ }
+ }
+ }
+ }
+
+ private class AccessoryReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
+ if (accessory != null) {
+ String action = intent.getAction();
+ if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ onAccessoryAttached(accessory);
+ } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) {
+ if (mAccessory != null && mAccessory.equals(accessory)) {
+ onAccessoryDetached(accessory);
+ }
+ } else if (action.equals(ACTION_USB_ACCESSORY_PERMISSION)) {
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+ Log.i(TAG, "Accessory permission granted: " + accessory);
+ onAccessoryAttached(accessory);
+ } else {
+ Log.e(TAG, "Accessory permission denied: " + accessory);
+ finish();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk b/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk
new file mode 100644
index 0000000..354e8c9
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/Android.mk
@@ -0,0 +1,35 @@
+# 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_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := AoapTestHostApp
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.xml
new file mode 100644
index 0000000..8cc470e
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/AndroidManifest.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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.hardware.usb.aoaphosttest" >
+ <application android:label="UsbAoapHostTestApp" >
+ <activity android:name=".UsbAoapHostTestActivity"
+ android:configChanges="keyboard|keyboardHidden" >
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ <meta-data
+ android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+ android:resource="@xml/usb_device_filter"/>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml
new file mode 100644
index 0000000..cc71cf9
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/layout/host.xml
@@ -0,0 +1,20 @@
+<?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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+</LinearLayout>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml
new file mode 100644
index 0000000..0509e89
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/res/xml/usb_device_filter.xml
@@ -0,0 +1,24 @@
+<?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>
+ <!-- Android USB accessory: accessory -->
+ <usb-device vendor-id="16601" product-id="11520" />
+ <!-- Android USB accessory: accessory + adb -->
+ <usb-device vendor-id="16601" product-id="11521" />
+ <!-- not suppoted by UsbService, but external host management can use this. -->
+ <usb-aoap-device model="AOAP Test App" manufacturer="Android" version="1.0" />
+</resources>
diff --git a/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java b/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java
new file mode 100644
index 0000000..6e2dc5d
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/AoapTestHost/src/com/android/hardware/usb/aoaphosttest/UsbAoapHostTestActivity.java
@@ -0,0 +1,165 @@
+/*
+ * 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.hardware.usb.aoaphosttest;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+public class UsbAoapHostTestActivity extends Activity {
+
+ private static final String TAG = UsbAoapHostTestActivity.class.getSimpleName();
+
+ private UsbManager mUsbManager;
+ private UsbStateReceiver mReceiver;
+ private UsbDevice mUsbDevice;
+ private UsbDeviceConnection mUsbConnection;
+ private ReaderThread mReaderThread;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.host);
+
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ mReceiver = new UsbStateReceiver();
+ registerReceiver(mReceiver, filter);
+
+ Intent intent = getIntent();
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ mUsbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ mUsbConnection = mUsbManager.openDevice(mUsbDevice);
+ mReaderThread = new ReaderThread(mUsbDevice, mUsbConnection);
+ mReaderThread.start();
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ if (mUsbConnection != null) {
+ mUsbConnection.close();
+ }
+ if (mReaderThread != null) {
+ mReaderThread.requestToQuit();
+ try {
+ mReaderThread.join(1000);
+ } catch (InterruptedException e) {
+ }
+ if (mReaderThread.isAlive()) { // reader thread stuck
+ throw new RuntimeException("ReaderThread still alive");
+ }
+ }
+ }
+
+ private 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;
+ }
+
+ private class ReaderThread extends Thread {
+ private boolean mShouldQuit = false;
+ private final UsbDevice mDevice;
+ private final UsbDeviceConnection mUsbConnection;
+ private final UsbEndpoint mBulkIn;
+ private final UsbEndpoint mBulkOut;
+ private final byte[] mBuffer = new byte[16384];
+
+ private ReaderThread(UsbDevice device, UsbDeviceConnection conn) {
+ super("AOAP");
+ mDevice = device;
+ mUsbConnection = conn;
+ UsbInterface iface = mDevice.getInterface(0);
+ // Setup bulk endpoints.
+ UsbEndpoint bulkIn = null;
+ UsbEndpoint bulkOut = null;
+ for (int i = 0; i < iface.getEndpointCount(); i++) {
+ UsbEndpoint ep = iface.getEndpoint(i);
+ if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
+ if (bulkIn == null) {
+ bulkIn = ep;
+ }
+ } else {
+ if (bulkOut == null) {
+ bulkOut = ep;
+ }
+ }
+ }
+ if (bulkIn == null || bulkOut == null) {
+ throw new IllegalStateException("Unable to find bulk endpoints");
+ }
+ mBulkIn = bulkIn;
+ mBulkOut = bulkOut;
+ }
+
+ private synchronized void requestToQuit() {
+ mShouldQuit = true;
+ }
+
+ private synchronized boolean shouldQuit() {
+ return mShouldQuit;
+ }
+
+ @Override
+ public void run() {
+ while (!shouldQuit()) {
+ int read = mUsbConnection.bulkTransfer(mBulkIn, mBuffer, mBuffer.length,
+ Integer.MAX_VALUE);
+ if (read < 0) {
+ throw new RuntimeException("bulkTransfer failed, read = " + read);
+ }
+ }
+ }
+ }
+
+ private class UsbStateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (isDevicesMatching(mUsbDevice, device)) {
+ finish();
+ }
+ }
+ }
+ }
+}
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk
new file mode 100644
index 0000000..2d6d6ea8
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/Android.mk
@@ -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.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+##################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := UsbHostExternalManagementTestApp
+
+LOCAL_PRIVILEGED_MODULE := true
+# TODO remove tests tag
+#LOCAL_MODULE_TAGS := tests
+#LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..97bbefb
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.hardware.usb.externalmanagementtest" >
+
+ <uses-permission android:name="android.permission.MANAGE_USB" />
+ <application android:label="UsbHostExternalManagementTestApp" >
+ <activity android:name=".UsbHostManagementActivity"
+ android:configChanges="keyboard|keyboardHidden" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml
new file mode 100644
index 0000000..5191184
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/layout/host_management.xml
@@ -0,0 +1,56 @@
+<?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="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/device_info_text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/current_device"
+ />
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal" >
+ <Button android:id="@+id/start_aoap_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/start_aoap"
+ />
+ <Button android:id="@+id/start_aoap_activity_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/start_aoap_activity"
+ />
+ <Button android:id="@+id/reset_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/usb_reset"
+ />
+ <Button android:id="@+id/finish_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/finish_app"
+ />
+ </LinearLayout>
+ <TextView android:id="@+id/aoap_apps_text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/aoap_app_msg"
+ />
+
+</LinearLayout>
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml
new file mode 100644
index 0000000..79d2c43
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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_title">UsbHostExternalManagementTestApp</string>
+ <string name="current_device">No device</string>
+ <string name="aoap_app_msg">AOAP App message</string>
+ <string name="usb_reset">Reset USB</string>
+ <string name="start_aoap">Start Test AOAP</string>
+ <string name="start_aoap_activity">Start Test AOAP Activity</string>
+ <string name="finish_app">Finish</string>
+</resources>
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java
new file mode 100644
index 0000000..89dc441
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/AoapInterface.java
@@ -0,0 +1,135 @@
+/**
+ * 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.hardware.usb.externalmanagementtest;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.util.Log;
+
+public 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;
+
+ 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,
+ AoapInterface.ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, 10000);
+ if (len != 2) {
+ return -1;
+ }
+ return (buffer[1] << 8) | buffer[0];
+ }
+
+ public static void sendString(UsbDeviceConnection conn, int index, String string) {
+ byte[] buffer = (string + "\0").getBytes();
+ int len = conn.controlTransfer(
+ UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
+ AoapInterface.ACCESSORY_SEND_STRING, 0, index,
+ buffer, buffer.length, 10000);
+ if (len != buffer.length) {
+ throw new RuntimeException("Failed to send string " + index + ": \"" + string + "\"");
+ } else {
+ Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
+ }
+ }
+
+ public static void sendAoapStart(UsbDeviceConnection conn) {
+ int len = conn.controlTransfer(
+ UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
+ AoapInterface.ACCESSORY_START, 0, 0, null, 0, 10000);
+ if (len < 0) {
+ throw new RuntimeException("control transfer for accessory start failed:" + len);
+ }
+ }
+
+ public static boolean isDeviceInAoapMode(UsbDevice device) {
+ 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/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java
new file mode 100644
index 0000000..1cb394e
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbDeviceStateController.java
@@ -0,0 +1,326 @@
+/*
+ * 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.hardware.usb.externalmanagementtest;
+
+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 java.util.LinkedList;
+
+import dalvik.system.CloseGuard;
+
+public class UsbDeviceStateController {
+
+ public interface UsbDeviceStateListener {
+ void onDeviceResetComplete(UsbDevice device);
+ void onAoapStartComplete(UsbDevice devie);
+ }
+
+ private static final String TAG = UsbDeviceStateController.class.getSimpleName();
+
+ private static final int MAX_USB_STATE_CHANGE_WAIT = 5;
+ private static final long USB_STATE_CHANGE_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;
+
+ public 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();
+ release();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public void startDeviceReset(UsbDevice device) {
+ mHandler.requestDeviceReset(device);
+ }
+
+ public void startAoap(AoapSwitchRequest request) {
+ mHandler.requestAoap(request);
+ }
+
+ private void doHandleDeviceReset(UsbDevice device) {
+ 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 devie: " + device);
+ } else {
+ completedDevice = device;
+ }
+ } finally {
+ conn.close();
+ }
+ }
+ }
+ mListener.onDeviceResetComplete(completedDevice);
+ }
+
+ private void doHandleAoapStart(AoapSwitchRequest request) {
+ UsbDevice device = request.device;
+ boolean isInAoap = AoapInterface.isDeviceInAoapMode(device);
+ if (isInAoap) {
+ device = resetUsbDeviceAndConfirmModeChange(device);
+ if (device == null) {
+ mListener.onAoapStartComplete(null);
+ return;
+ }
+ }
+ UsbDeviceConnection connection = openConnection(device);
+ 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);
+ if (device == null) {
+ mListener.onAoapStartComplete(null);
+ }
+ if (!AoapInterface.isDeviceInAoapMode(device)) {
+ Log.w(TAG, "Device not in AOAP mode after switching: " + device);
+ mListener.onAoapStartComplete(device);
+ }
+ mListener.onAoapStartComplete(device);
+ }
+
+ private UsbDevice resetUsbDeviceAndConfirmModeChange(UsbDevice device) {
+ int retry = 0;
+ boolean removalDetected = false;
+ while (retry < MAX_USB_STATE_CHANGE_WAIT) {
+ UsbDeviceConnection connNow = openConnection(device);
+ if (connNow == null) {
+ removalDetected = true;
+ break;
+ }
+ connNow.resetDevice();
+ connNow.close();
+ synchronized (mUsbConnectionChangeWait) {
+ try {
+ mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_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_STATE_CHANGE_WAIT) {
+ synchronized (mUsbConnectionChangeWait) {
+ try {
+ mUsbConnectionChangeWait.wait(USB_STATE_CHANGE_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 final int MSG_RESET_DEVICE = 1;
+ private 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;
+
+ public 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/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java
new file mode 100644
index 0000000..2d9226f
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbHostManagementActivity.java
@@ -0,0 +1,246 @@
+/*
+ * 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.hardware.usb.externalmanagementtest;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.net.nsd.NsdManager.DiscoveryListener;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.hardware.usb.externalmanagementtest.UsbDeviceStateController.AoapSwitchRequest;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+public class UsbHostManagementActivity extends Activity
+ implements UsbDeviceStateController.UsbDeviceStateListener {
+
+ private static final String TAG = UsbHostManagementActivity.class.getSimpleName();
+
+ private static final String AOAP_APP_PACKAGE_NAME = "com.android.hardware.usb.aoaphosttest";
+ private static final String AOAP_APP_ACTIVITY_NAME =
+ "com.android.hardware.usb.aoaphosttest.UsbAoapHostTestActivity";
+
+ private TextView mDeviceInfoText;
+ private Button mStartAoapButton;
+ private Button mStartAoapActivityButton;
+ private TextView mAoapAppLog;
+ private Button mResetUsbButton;
+ private Button mFinishButton;
+ private UsbDevice mUsbDevice = null;
+ private final UsbDeviceConnectionListener mConnectionListener =
+ new UsbDeviceConnectionListener();
+ private UsbDeviceStateController mUsbDeviceStateController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.host_management);
+ mDeviceInfoText = (TextView) findViewById(R.id.device_info_text);
+ mStartAoapButton = (Button) findViewById(R.id.start_aoap_button);
+ mStartAoapActivityButton = (Button) findViewById(R.id.start_aoap_activity_button);
+ mAoapAppLog = (TextView) findViewById(R.id.aoap_apps_text);
+ mResetUsbButton = (Button) findViewById(R.id.reset_button);
+ mFinishButton = (Button) findViewById(R.id.finish_button);
+
+ Intent intent = getIntent();
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ mUsbDevice = intent.<UsbDevice>getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ }
+ UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ if (mUsbDevice == null) {
+ LinkedList<UsbDevice> devices = UsbUtil.findAllPossibleAndroidDevices(usbManager);
+ if (devices.size() > 0) {
+ mUsbDevice = devices.getLast();
+ }
+ }
+ updateDevice(mUsbDevice);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ registerReceiver(mConnectionListener, filter);
+
+ mStartAoapButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mUsbDevice == null) {
+ return;
+ }
+ startAoap(mUsbDevice);
+ }
+ });
+ mStartAoapActivityButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mUsbDevice == null) {
+ return;
+ }
+ startAoapActivity(mUsbDevice);
+ }
+ });
+ mResetUsbButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mUsbDevice == null) {
+ return;
+ }
+ resetDevice(mUsbDevice);
+ }
+ });
+ mFinishButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ mUsbDeviceStateController = new UsbDeviceStateController(this, this, usbManager);
+ mUsbDeviceStateController.init();
+ }
+
+
+ private void startAoap(UsbDevice device) {
+ AoapSwitchRequest request = new AoapSwitchRequest(device, "Android", "AOAP Test App", "",
+ "1.0", "", "");
+ mUsbDeviceStateController.startAoap(request);
+ }
+
+ private void startAoapActivity(UsbDevice device) {
+ if (!AoapInterface.isDeviceInAoapMode(device)) {
+ Log.w(TAG, "Device not in AOAP mode:" + device);
+ return;
+ }
+ PackageManager pm = getPackageManager();
+ PackageInfo pi = null;
+ try {
+ pi = pm.getPackageInfo(AOAP_APP_PACKAGE_NAME, 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "AOAP Test app not found:" + AOAP_APP_PACKAGE_NAME);
+ }
+ int uid = pi.applicationInfo.uid;
+ UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ usbManager.grantPermission(device, uid);
+ Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ intent.setComponent(new ComponentName(AOAP_APP_PACKAGE_NAME, AOAP_APP_ACTIVITY_NAME));
+ startActivity(intent);
+ }
+
+ private void resetDevice(UsbDevice device) {
+ Log.i(TAG, "resetDevice");
+ mUsbDeviceStateController.startDeviceReset(device);
+ }
+
+ private void dumpUsbDevices() {
+ UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ HashMap<String, UsbDevice> devices = usbManager.getDeviceList();
+ StringBuilder sb = new StringBuilder();
+ sb.append("Usb devices\n");
+ for (UsbDevice device : devices.values()) {
+ sb.append(device.toString() + "\n");
+ }
+ Log.i(TAG, sb.toString());
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.i(TAG, "onDestroy");
+ super.onDestroy();
+ unregisterReceiver(mConnectionListener);
+ mUsbDeviceStateController.release();
+ }
+
+ private void handleUsbDeviceAttached(UsbDevice device) {
+ boolean deviceReplaced = false;
+ if (mUsbDevice == null) {
+ deviceReplaced = true;
+ } else {
+ UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+ if (!UsbUtil.isDeviceConnected(usbManager, mUsbDevice)) {
+ deviceReplaced = true;
+ }
+ }
+ if (deviceReplaced) {
+ Log.i(TAG, "device attached:" + device);
+ updateDevice(device);
+ }
+ }
+
+ private void handleUsbDeviceDetached(UsbDevice device) {
+ if (mUsbDevice != null && UsbUtil.isDevicesMatching(mUsbDevice, device)) {
+ Log.i(TAG, "device removed ");
+ updateDevice(device);
+ }
+ }
+
+ private void updateDevice(UsbDevice device) {
+ mUsbDevice = device;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mUsbDevice == null) {
+ mDeviceInfoText.setText("disconnected");
+ } else {
+ mDeviceInfoText.setText(mUsbDevice.toString());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onDeviceResetComplete(UsbDevice device) {
+ updateDevice(device);
+ }
+
+
+ @Override
+ public void onAoapStartComplete(UsbDevice device) {
+ updateDevice(device);
+ }
+
+ private class UsbDeviceConnectionListener 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);
+ }
+ }
+ }
+}
diff --git a/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java
new file mode 100644
index 0000000..8d0f73e
--- /dev/null
+++ b/tests/UsbHostExternalManagmentTest/UsbHostExternalManagmentTestApp/src/com/android/hardware/usb/externalmanagementtest/UsbUtil.java
@@ -0,0 +1,87 @@
+/*
+ * 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.hardware.usb.externalmanagementtest;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import android.content.Context;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.text.TextUtils;
+
+public 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();
+ // more thorough check can be added, later
+ if (AOAP_INTERFACE_NAME.equals(interfaceName) ||
+ ADB_INTERFACE_NAME.equals(interfaceName) ||
+ MTP_INTERFACE_NAME.equals(interfaceName)) {
+ 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;
+ }
+}