diff options
6 files changed, 192 insertions, 6 deletions
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java index 5222826bed48..f6e4276c5988 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java @@ -67,6 +67,10 @@ class MtpDocument { this.mThumbSize = thumbSize; } + int getSize() { + return mSize; + } + String getMimeType() { // TODO: Add complete list of mime types. switch (mFormat) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index fab512563448..bbb8063b4832 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -53,6 +53,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpManager mMtpManager; private ContentResolver mResolver; + private PipeManager mPipeManager; /** * Provides singleton instance to MtpDocumentsService. @@ -66,6 +67,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { sSingleton = this; mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); + mPipeManager = new PipeManager(); + return true; } @@ -156,9 +159,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { } @Override - public ParcelFileDescriptor openDocument(String documentId, String mode, - CancellationSignal signal) throws FileNotFoundException { - throw new FileNotFoundException(); + public ParcelFileDescriptor openDocument( + String documentId, String mode, CancellationSignal signal) + throws FileNotFoundException { + if (!"r".equals(mode) && !"w".equals(mode)) { + // TODO: Support seekable file. + throw new UnsupportedOperationException("The provider does not support seekable file."); + } + final Identifier identifier = Identifier.createFromDocumentId(documentId); + try { + final MtpDocument document = + mMtpManager.getDocument(identifier.mDeviceId, identifier.mObjectHandle); + return mPipeManager.readDocument(mMtpManager, identifier, document.getSize()); + } catch (IOException error) { + throw new FileNotFoundException(error.getMessage()); + } } void openDevice(int deviceId) throws IOException { @@ -192,8 +207,6 @@ public class MtpDocumentsProvider extends DocumentsProvider { private void notifyRootsChange() { mResolver.notifyChange( - DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), - null, - false); + DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), null, false); } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index e34312c6c3d3..f803744d6ba8 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -103,6 +103,12 @@ class MtpManager { return new MtpDocument(device.getObjectInfo(objectHandle)); } + synchronized byte[] getObject(int deviceId, int objectHandle, int expectedSize) + throws IOException { + final MtpDevice device = getDevice(deviceId); + return device.getObject(objectHandle, expectedSize); + } + private MtpDevice getDevice(int deviceId) throws IOException { final MtpDevice device = mDevices.get(deviceId); if (device == null) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java new file mode 100644 index 000000000000..a2c7772fb4db --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mtp; + +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +class PipeManager { + final ExecutorService mExecutor; + + PipeManager() { + this(Executors.newCachedThreadPool()); + } + + PipeManager(ExecutorService executor) { + this.mExecutor = executor; + } + + ParcelFileDescriptor readDocument( + final MtpManager model, + final Identifier identifier, + final int expectedSize) throws IOException { + final Task task = new Task() { + @Override + byte[] getBytes() throws IOException { + // TODO: Use importFile to ParcelFileDescripter after implementing this. + return model.getObject( + identifier.mDeviceId, identifier.mObjectHandle, expectedSize); + } + }; + mExecutor.execute(task); + return task.getReadingFileDescriptor(); + } + + private static abstract class Task implements Runnable { + private final ParcelFileDescriptor[] mDescriptors; + + Task() throws IOException { + mDescriptors = ParcelFileDescriptor.createReliablePipe(); + } + + abstract byte[] getBytes() throws IOException; + + @Override + public void run() { + try (final ParcelFileDescriptor.AutoCloseOutputStream stream = + new ParcelFileDescriptor.AutoCloseOutputStream(mDescriptors[1])) { + try { + final byte[] bytes = getBytes(); + stream.write(bytes); + } catch (IOException error) { + mDescriptors[1].closeWithError("Failed to load bytes."); + return; + } + } catch (IOException closeError) { + Log.d(MtpDocumentsProvider.TAG, closeError.getMessage()); + } + } + + ParcelFileDescriptor getReadingFileDescriptor() { + return mDescriptors[0]; + } + } + + void close() { + mExecutor.shutdown(); + } +} diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java new file mode 100644 index 000000000000..3c295eeb2608 --- /dev/null +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mtp; + +import android.os.ParcelFileDescriptor; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +@SmallTest +public class PipeManagerTest extends AndroidTestCase { + public void testReadDocument_basic() throws Exception { + final TestMtpManager mtpManager = new TestMtpManager(getContext()); + final byte[] expectedBytes = new byte[] { 'h', 'e', 'l', 'l', 'o' }; + mtpManager.setObjectBytes(0, 1, 5, expectedBytes); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final PipeManager pipeManager = new PipeManager(executor); + final ParcelFileDescriptor descriptor = pipeManager.readDocument( + mtpManager, new Identifier(0, 0, 1), 5); + try (final ParcelFileDescriptor.AutoCloseInputStream stream = + new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) { + final byte[] results = new byte[100]; + assertEquals(5, stream.read(results)); + for (int i = 0; i < 5; i++) { + assertEquals(expectedBytes[i], results[i]); + } + } + } + + public void testReadDocument_error() throws Exception { + final TestMtpManager mtpManager = new TestMtpManager(getContext()); + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final PipeManager pipeManager = new PipeManager(executor); + final ParcelFileDescriptor descriptor = + pipeManager.readDocument(mtpManager, new Identifier(0, 0, 1), 5); + executor.awaitTermination(1000, TimeUnit.MILLISECONDS); + try { + descriptor.checkError(); + fail(); + } catch (Throwable error) { + assertTrue(error instanceof IOException); + } + } +} diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index 08ceb293c3a6..43f4bb2d8649 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -35,6 +35,7 @@ public class TestMtpManager extends MtpManager { private final Set<Integer> mOpenedDevices = new TreeSet<Integer>(); private final Map<Integer, MtpRoot[]> mRoots = new HashMap<Integer, MtpRoot[]>(); private final Map<String, MtpDocument> mDocuments = new HashMap<String, MtpDocument>(); + private final Map<String, byte[]> mObjectBytes = new HashMap<String, byte[]>(); TestMtpManager(Context context) { super(context); @@ -52,6 +53,10 @@ public class TestMtpManager extends MtpManager { mDocuments.put(pack(deviceId, objectHandle), document); } + void setObjectBytes(int deviceId, int objectHandle, int expectedSize, byte[] bytes) { + mObjectBytes.put(pack(deviceId, objectHandle, expectedSize), bytes); + } + @Override void openDevice(int deviceId) throws IOException { if (!mValidDevices.contains(deviceId) || mOpenedDevices.contains(deviceId)) { @@ -83,6 +88,16 @@ public class TestMtpManager extends MtpManager { } @Override + byte[] getObject(int deviceId, int storageId, int expectedSize) throws IOException { + final String key = pack(deviceId, storageId, expectedSize); + if (mObjectBytes.containsKey(key)) { + return mObjectBytes.get(key); + } else { + throw new IOException("getObject error: " + key); + } + } + + @Override int[] getOpenedDeviceIds() { int i = 0; final int[] result = new int[mOpenedDevices.size()]; |