diff options
6 files changed, 107 insertions, 25 deletions
diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk index 67bbf782dc96..b31b0b13ec07 100644 --- a/packages/MtpDocumentsProvider/Android.mk +++ b/packages/MtpDocumentsProvider/Android.mk @@ -7,6 +7,7 @@ LOCAL_PACKAGE_NAME := MtpDocumentsProvider LOCAL_CERTIFICATE := media LOCAL_PRIVILEGED_MODULE := true LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni +LOCAL_PROGUARD_FLAG_FILES := proguard.flags include $(BUILD_PACKAGE) include $(call all-makefiles-under, $(LOCAL_PATH)) diff --git a/packages/MtpDocumentsProvider/proguard.flags b/packages/MtpDocumentsProvider/proguard.flags new file mode 100644 index 000000000000..e6601212858e --- /dev/null +++ b/packages/MtpDocumentsProvider/proguard.flags @@ -0,0 +1,4 @@ +# Keeps methods that are invoked by JNI. +-keepclassmembers class * { + @com.android.mtp.annotations.UsedByNative *; +} diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java index 5ffd7cf27b5f..01d1301871bb 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java @@ -21,25 +21,27 @@ import android.os.Process; import android.os.storage.StorageManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.mtp.annotations.UsedByNative; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -/** - * TODO: Remove VisibleForTesting class. - */ -@VisibleForTesting public class AppFuse { static { System.loadLibrary("appfuse_jni"); } + /** + * Max read amount specified at the FUSE kernel implementation. + * The value is copied from sdcard.c. + */ + static final int MAX_READ = 128 * 1024; + private final String mName; private final Callback mCallback; private final Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; - @VisibleForTesting AppFuse(String name, Callback callback) { mName = name; mCallback = callback; @@ -51,7 +53,6 @@ public class AppFuse { }); } - @VisibleForTesting void mount(StorageManager storageManager) { mDeviceFd = storageManager.mountAppFuse(mName); mMessageThread.start(); @@ -72,11 +73,6 @@ public class AppFuse { } } - /** - * @param i - * @throws FileNotFoundException - */ - @VisibleForTesting public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { return ParcelFileDescriptor.open(new File( getMountPoint(), @@ -94,7 +90,7 @@ public class AppFuse { byte[] getObjectBytes(int inode, long offset, int size) throws IOException; } - @VisibleForTesting + @UsedByNative("com_android_mtp_AppFuse.cpp") private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); @@ -103,8 +99,11 @@ public class AppFuse { } } - @VisibleForTesting + @UsedByNative("com_android_mtp_AppFuse.cpp") private byte[] getObjectBytes(int inode, long offset, int size) { + if (offset < 0 || size < 0 || size > MAX_READ) { + return null; + } try { return mCallback.getObjectBytes(inode, offset, size); } catch (IOException e) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 3573536b37e6..afef3de38618 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -16,6 +16,8 @@ package com.android.mtp; +import static com.android.internal.util.Preconditions.checkArgument; + import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; @@ -26,6 +28,7 @@ import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.os.storage.StorageManager; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; @@ -34,6 +37,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; @@ -68,6 +72,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private RootScanner mRootScanner; private Resources mResources; private MtpDatabase mDatabase; + private AppFuse mAppFuse; /** * Provides singleton instance to MtpDocumentsService. @@ -85,6 +90,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); + mAppFuse = new AppFuse(TAG, new AppFuseCallback()); + // TODO: Mount AppFuse on demands. + mAppFuse.mount(getContext().getSystemService(StorageManager.class)); resume(); return true; } @@ -147,16 +155,27 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { switch (mode) { case "r": - return getPipeManager(identifier).readDocument(mMtpManager, identifier); + final long fileSize = getFileSize(documentId); + // MTP getPartialObject operation does not support files that are larger than 4GB. + // Fallback to non-seekable file descriptor. + // TODO: Use getPartialObject64 for MTP devices that support Android vendor + // extension. + if (fileSize <= 0xffffffff) { + return mAppFuse.openFile(Integer.parseInt(documentId)); + } else { + return getPipeManager(identifier).readDocument(mMtpManager, identifier); + } case "w": // TODO: Clear the parent document loader task (if exists) and call notify // when writing is completed. return getPipeManager(identifier).writeDocument( getContext(), mMtpManager, identifier); - default: - // TODO: Add support for seekable files. + case "rw": + // TODO: Add support for "rw" mode. throw new UnsupportedOperationException( - "The provider does not support seekable file."); + "The provider does not support 'rw' mode."); + default: + throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); } } catch (IOException error) { throw new FileNotFoundException(error.getMessage()); @@ -330,6 +349,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { return getDeviceToolkit(identifier.mDeviceId).mDocumentLoader; } + private long getFileSize(String documentId) throws FileNotFoundException { + final Cursor cursor = mDatabase.queryDocument( + documentId, + MtpDatabase.strings(Document.COLUMN_SIZE, Document.COLUMN_DISPLAY_NAME)); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } else { + throw new FileNotFoundException(); + } + } finally { + cursor.close(); + } + } + private static class DeviceToolkit { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; @@ -339,4 +373,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDocumentLoader = new DocumentLoader(manager, resolver, database); } } + + private class AppFuseCallback implements AppFuse.Callback { + final byte[] mBytes = new byte[AppFuse.MAX_READ]; + + @Override + public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { + final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); + mMtpManager.getPartialObject( + identifier.mDeviceId, identifier.mObjectHandle, (int) offset, size, mBytes); + return mBytes; + } + + @Override + public long getFileSize(int inode) throws FileNotFoundException { + return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); + } + } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java new file mode 100644 index 000000000000..a7f295f670b3 --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java @@ -0,0 +1,31 @@ +/* + * 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.mtp.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation that shows the method is used by JNI. + */ +@Target(ElementType.METHOD) +public @interface UsedByNative { + /** + * JNI file name that uses the method. + */ + String value(); +} diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java index 76bd2b546472..5e1a796d6def 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java @@ -28,13 +28,9 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; -/** - * TODO: Enable this test after adding SELinux policies for appfuse. - */ @MediumTest public class AppFuseTest extends AndroidTestCase { - - public void disabled_testMount() throws ErrnoException, InterruptedException { + public void testMount() throws ErrnoException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); @@ -45,7 +41,7 @@ public class AppFuseTest extends AndroidTestCase { assertTrue(1 != Os.stat(file.getPath()).st_ino); } - public void disabled_testOpenFile() throws IOException { + public void testOpenFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse( @@ -65,7 +61,7 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); } - public void disabled_testOpenFile_error() { + public void testOpenFile_error() { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse("test", new TestCallback()); @@ -79,7 +75,7 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); } - public void disabled_testReadFile() throws IOException { + public void testReadFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; |