Merge "Start to use app fuse in MtpDocumentsProvider."

This commit is contained in:
Daichi Hirono
2016-01-19 03:24:00 +00:00
committed by Android (Google) Code Review
6 changed files with 107 additions and 25 deletions

View 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))

View File

@@ -0,0 +1,4 @@
# Keeps methods that are invoked by JNI.
-keepclassmembers class * {
@com.android.mtp.annotations.UsedByNative *;
}

View 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) {

View 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));
}
}
}

View File

@@ -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();
}

View 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' };