Merge "Add readEvent method to MtpDevice."
diff --git a/api/current.txt b/api/current.txt
index f963c17..2d133aa 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18133,6 +18133,7 @@
method public boolean importFile(int, java.lang.String);
method public boolean importFile(int, android.os.ParcelFileDescriptor);
method public boolean open(android.hardware.usb.UsbDeviceConnection);
+ method public android.mtp.MtpEvent readEvent(android.os.CancellationSignal);
method public boolean sendObject(int, int, android.os.ParcelFileDescriptor);
method public android.mtp.MtpObjectInfo sendObjectInfo(android.mtp.MtpObjectInfo);
}
@@ -18144,6 +18145,11 @@
method public final java.lang.String getVersion();
}
+ public class MtpEvent {
+ ctor public MtpEvent();
+ method public int getEventCode();
+ }
+
public final class MtpObjectInfo {
method public final int getAssociationDesc();
method public final int getAssociationType();
diff --git a/api/system-current.txt b/api/system-current.txt
index 1084f30..93551da 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19645,6 +19645,7 @@
method public boolean importFile(int, java.lang.String);
method public boolean importFile(int, android.os.ParcelFileDescriptor);
method public boolean open(android.hardware.usb.UsbDeviceConnection);
+ method public android.mtp.MtpEvent readEvent(android.os.CancellationSignal);
method public boolean sendObject(int, int, android.os.ParcelFileDescriptor);
method public android.mtp.MtpObjectInfo sendObjectInfo(android.mtp.MtpObjectInfo);
}
@@ -19656,6 +19657,11 @@
method public final java.lang.String getVersion();
}
+ public class MtpEvent {
+ ctor public MtpEvent();
+ method public int getEventCode();
+ }
+
public final class MtpObjectInfo {
method public final int getAssociationDesc();
method public final int getAssociationType();
diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java
index 3cd157e..95cb520 100644
--- a/media/java/android/mtp/MtpDevice.java
+++ b/media/java/android/mtp/MtpDevice.java
@@ -18,6 +18,8 @@
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
/**
@@ -47,7 +49,7 @@
/**
* Opens the MTP device. Once the device is open it takes ownership of the
- * {@link android.hardware.usb.UsbDeviceConnection}.
+ * {@link android.hardware.usb.UsbDeviceConnection}.
* The connection will be closed when you call {@link #close()}
* The connection will also be closed if this method fails.
*
@@ -278,6 +280,38 @@
return native_send_object_info(info);
}
+ /**
+ * Reads an event from the device. It blocks the current thread until it gets an event.
+ * It throws OperationCanceledException if it is cancelled by signal.
+ *
+ * @param signal signal for cancellation
+ * @return obtained event
+ */
+ public MtpEvent readEvent(CancellationSignal signal) {
+ final int handle = native_submit_event_request();
+
+ if (handle < 0) {
+ throw new IllegalStateException("Other thread is reading an event.");
+ }
+
+ if (signal != null) {
+ signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
+ @Override
+ public void onCancel() {
+ native_discard_event_request(handle);
+ }
+ });
+ }
+
+ try {
+ return native_reap_event_request(handle);
+ } finally {
+ if (signal != null) {
+ signal.setOnCancelListener(null);
+ }
+ }
+ }
+
// used by the JNI code
private long mNativeContext;
@@ -297,4 +331,7 @@
private native boolean native_import_file(int objectHandle, int fd);
private native boolean native_send_object(int objectHandle, int size, int fd);
private native MtpObjectInfo native_send_object_info(MtpObjectInfo info);
+ private native int native_submit_event_request();
+ private native MtpEvent native_reap_event_request(int handle);
+ private native void native_discard_event_request(int handle);
}
diff --git a/media/java/android/mtp/MtpEvent.java b/media/java/android/mtp/MtpEvent.java
new file mode 100644
index 0000000..0fa7d93
--- /dev/null
+++ b/media/java/android/mtp/MtpEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mtp;
+
+/**
+ * This class encapsulates information about a MTP event.
+ */
+public class MtpEvent {
+ private int mEventCode;
+
+ /**
+ * Returns event code of MTP event.
+ *
+ * @return event code
+ */
+ public int getEventCode() { return mEventCode; }
+}
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index f11329c..3f4d183 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -46,10 +46,14 @@
jclass clazz_deviceInfo;
jclass clazz_storageInfo;
jclass clazz_objectInfo;
+jclass clazz_event;
+jclass clazz_io_exception;
+jclass clazz_operation_canceled_exception;
jmethodID constructor_deviceInfo;
jmethodID constructor_storageInfo;
jmethodID constructor_objectInfo;
+jmethodID constructor_event;
// MtpDeviceInfo fields
static jfieldID field_deviceInfo_manufacturer;
@@ -86,6 +90,9 @@
static jfieldID field_objectInfo_dateModified;
static jfieldID field_objectInfo_keywords;
+// MtpEvent fields
+static jfieldID field_event_eventCode;
+
MtpDevice* get_device_from_object(JNIEnv* env, jobject javaDevice)
{
return (MtpDevice*)env->GetLongField(javaDevice, field_context);
@@ -488,6 +495,42 @@
return result;
}
+static jint android_mtp_MtpDevice_submit_event_request(JNIEnv *env, jobject thiz)
+{
+ MtpDevice* const device = get_device_from_object(env, thiz);
+ if (!device) {
+ env->ThrowNew(clazz_io_exception, "");
+ return -1;
+ }
+ return device->submitEventRequest();
+}
+
+static jobject android_mtp_MtpDevice_reap_event_request(JNIEnv *env, jobject thiz, jint seq)
+{
+ MtpDevice* const device = get_device_from_object(env, thiz);
+ if (!device) {
+ env->ThrowNew(clazz_io_exception, "");
+ return NULL;
+ }
+ const int eventCode = device->reapEventRequest(seq);
+ if (eventCode <= 0) {
+ env->ThrowNew(clazz_operation_canceled_exception, "");
+ return NULL;
+ }
+ jobject result = env->NewObject(clazz_event, constructor_event);
+ env->SetIntField(result, field_event_eventCode, eventCode);
+ return result;
+}
+
+static void android_mtp_MtpDevice_discard_event_request(JNIEnv *env, jobject thiz, jint seq)
+{
+ MtpDevice* const device = get_device_from_object(env, thiz);
+ if (!device) {
+ return;
+ }
+ device->discardEventRequest(seq);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -513,7 +556,11 @@
{"native_import_file", "(II)Z",(void *)android_mtp_MtpDevice_import_file_to_fd},
{"native_send_object", "(III)Z",(void *)android_mtp_MtpDevice_send_object},
{"native_send_object_info", "(Landroid/mtp/MtpObjectInfo;)Landroid/mtp/MtpObjectInfo;",
- (void *)android_mtp_MtpDevice_send_object_info}
+ (void *)android_mtp_MtpDevice_send_object_info},
+ {"native_submit_event_request", "()I", (void *)android_mtp_MtpDevice_submit_event_request},
+ {"native_reap_event_request", "(I)Landroid/mtp/MtpEvent;",
+ (void *)android_mtp_MtpDevice_reap_event_request},
+ {"native_discard_event_request", "(I)V", (void *)android_mtp_MtpDevice_discard_event_request},
};
int register_android_mtp_MtpDevice(JNIEnv *env)
@@ -703,6 +750,23 @@
}
clazz_objectInfo = (jclass)env->NewGlobalRef(clazz);
+ clazz = env->FindClass("android/mtp/MtpEvent");
+ if (clazz == NULL) {
+ ALOGE("Can't find android/mtp/MtpEvent");
+ return -1;
+ }
+ constructor_event = env->GetMethodID(clazz, "<init>", "()V");
+ if (constructor_event == NULL) {
+ ALOGE("Can't find android/mtp/MtpEvent constructor");
+ return -1;
+ }
+ field_event_eventCode = env->GetFieldID(clazz, "mEventCode", "I");
+ if (field_event_eventCode == NULL) {
+ ALOGE("Can't find MtpObjectInfo.mEventCode");
+ return -1;
+ }
+ clazz_event = (jclass)env->NewGlobalRef(clazz);
+
clazz = env->FindClass("android/mtp/MtpDevice");
if (clazz == NULL) {
ALOGE("Can't find android/mtp/MtpDevice");
@@ -713,6 +777,18 @@
ALOGE("Can't find MtpDevice.mNativeContext");
return -1;
}
+ clazz = env->FindClass("java/io/IOException");
+ if (clazz == NULL) {
+ ALOGE("Can't find java.io.IOException");
+ return -1;
+ }
+ clazz_io_exception = (jclass)env->NewGlobalRef(clazz);
+ clazz = env->FindClass("android/os/OperationCanceledException");
+ if (clazz == NULL) {
+ ALOGE("Can't find android.os.OperationCanceledException");
+ return -1;
+ }
+ clazz_operation_canceled_exception = (jclass)env->NewGlobalRef(clazz);
return AndroidRuntime::registerNativeMethods(env,
"android/mtp/MtpDevice", gMethods, NELEM(gMethods));
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 7cc7413..af7f691 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -22,12 +22,14 @@
import android.hardware.usb.UsbManager;
import android.mtp.MtpConstants;
import android.mtp.MtpDevice;
+import android.mtp.MtpEvent;
import android.mtp.MtpObjectInfo;
+import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -188,7 +190,13 @@
}
}
- private MtpDevice getDevice(int deviceId) throws IOException {
+ @VisibleForTesting
+ MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException {
+ final MtpDevice device = getDevice(deviceId);
+ return device.readEvent(signal);
+ }
+
+ private synchronized MtpDevice getDevice(int deviceId) throws IOException {
final MtpDevice device = mDevices.get(deviceId);
if (device == null) {
throw new IOException("USB device " + deviceId + " is not opened.");
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 2c1f115..5547771 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -16,35 +16,101 @@
package com.android.mtp;
+import android.app.PendingIntent;
+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.CancellationSignal;
+import android.os.OperationCanceledException;
import android.test.InstrumentationTestCase;
+import java.io.IOException;
import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
+@RealDeviceTest
public class MtpManagerTest extends InstrumentationTestCase {
- @RealDeviceTest
- public void testBasic() throws Exception {
- final UsbDevice usbDevice = findDevice();
- final MtpManager manager = new MtpManager(getContext());
- manager.openDevice(usbDevice.getDeviceId());
- waitForStorages(manager, usbDevice.getDeviceId());
- manager.closeDevice(usbDevice.getDeviceId());
+ private static final String ACTION_USB_PERMISSION =
+ "com.android.mtp.USB_PERMISSION";
+ private static final int TIMEOUT_MS = 1000;
+ UsbManager mUsbManager;
+ MtpManager mManager;
+ UsbDevice mUsbDevice;
+ int mRequest;
+
+ @Override
+ public void setUp() throws Exception {
+ mUsbManager = getContext().getSystemService(UsbManager.class);
+ mUsbDevice = findDevice();
+ mManager = new MtpManager(getContext());
+ mManager.openDevice(mUsbDevice.getDeviceId());
+ waitForStorages(mManager, mUsbDevice.getDeviceId());
+ }
+
+ @Override
+ public void tearDown() throws IOException {
+ mManager.closeDevice(mUsbDevice.getDeviceId());
+ }
+
+ public void testCancelEvent() throws Exception {
+ final CancellationSignal signal = new CancellationSignal();
+ final Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ mManager.readEvent(mUsbDevice.getDeviceId(), signal);
+ } catch (OperationCanceledException | IOException e) {
+ show(e.getMessage());
+ }
+ }
+ };
+ thread.start();
+ Thread.sleep(TIMEOUT_MS);
+ signal.cancel();
+ thread.join(TIMEOUT_MS);
+ }
+
+ private void requestPermission(UsbDevice device) throws InterruptedException {
+ if (mUsbManager.hasPermission(device)) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ latch.countDown();
+ getInstrumentation().getTargetContext().unregisterReceiver(this);
+ }
+ };
+ getInstrumentation().getTargetContext().registerReceiver(
+ receiver, new IntentFilter(ACTION_USB_PERMISSION));
+ mUsbManager.requestPermission(device, PendingIntent.getBroadcast(
+ getInstrumentation().getTargetContext(),
+ 0 /* requstCode */,
+ new Intent(ACTION_USB_PERMISSION),
+ 0 /* flags */));
+ latch.await();
+ assertTrue(mUsbManager.hasPermission(device));
}
private UsbDevice findDevice() throws InterruptedException {
- final UsbManager usbManager = getContext().getSystemService(UsbManager.class);
while (true) {
- final HashMap<String,UsbDevice> devices = usbManager.getDeviceList();
+ final HashMap<String,UsbDevice> devices = mUsbManager.getDeviceList();
if (devices.size() == 0) {
show("Wait for devices.");
Thread.sleep(1000);
continue;
}
final UsbDevice device = devices.values().iterator().next();
- final UsbDeviceConnection connection = usbManager.openDevice(device);
+ requestPermission(device);
+ final UsbDeviceConnection connection = mUsbManager.openDevice(device);
+ if (connection == null) {
+ fail("Cannot open USB connection.");
+ }
for (int i = 0; i < device.getInterfaceCount(); i++) {
// Since the test runs real environment, we need to call claim interface with
// force = true to rob interfaces from other applications.
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/RealDeviceTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/RealDeviceTest.java
index 9641ad7..22daaf2 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/RealDeviceTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/RealDeviceTest.java
@@ -17,7 +17,10 @@
package com.android.mtp;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
@interface RealDeviceTest {}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
index 9824d28..a243375 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestResultInstrumentation.java
@@ -45,8 +45,11 @@
}
private void show(String tag, Test test, Throwable t) {
+ String message = "";
+ if (t != null && t.getMessage() != null) {
+ message = t.getMessage();
+ }
TestResultActivity.show(
- getContext(),
- String.format("[%s] %s %s", tag, test.toString(), t != null ? t.getMessage() : ""));
+ getContext(), String.format("[%s] %s %s", tag, test.toString(), message));
}
}