OSDN Git Service

Start to use app fuse in MtpDocumentsProvider.
authorDaichi Hirono <hirono@google.com>
Mon, 11 Jan 2016 09:07:01 +0000 (18:07 +0900)
committerDaichi Hirono <hirono@google.com>
Tue, 19 Jan 2016 02:20:59 +0000 (11:20 +0900)
BUG=25756419

Change-Id: I050e7cf7523926710291875737602e95c47be088

packages/MtpDocumentsProvider/Android.mk
packages/MtpDocumentsProvider/proguard.flags [new file with mode: 0644]
packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java [new file with mode: 0644]
packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java

index 67bbf78..b31b0b1 100644 (file)
@@ -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 (file)
index 0000000..e660121
--- /dev/null
@@ -0,0 +1,4 @@
+# Keeps methods that are invoked by JNI.
+-keepclassmembers class * {
+  @com.android.mtp.annotations.UsedByNative *;
+}
index 5ffd7cf..01d1301 100644 (file)
@@ -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) {
index 3573536..afef3de 100644 (file)
@@ -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 (file)
index 0000000..a7f295f
--- /dev/null
@@ -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();
+}
index 76bd2b5..5e1a796 100644 (file)
@@ -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' };