Merge "Start to use app fuse in MtpDocumentsProvider."
This commit is contained in:
committed by
Android (Google) Code Review
commit
4ba6303a80
@@ -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))
|
||||
|
||||
4
packages/MtpDocumentsProvider/proguard.flags
Normal file
4
packages/MtpDocumentsProvider/proguard.flags
Normal file
@@ -0,0 +1,4 @@
|
||||
# Keeps methods that are invoked by JNI.
|
||||
-keepclassmembers class * {
|
||||
@com.android.mtp.annotations.UsedByNative *;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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' };
|
||||
|
||||
Reference in New Issue
Block a user