Loading packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +162 −11 Original line number Diff line number Diff line Loading @@ -30,15 +30,13 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "nativehelper/ScopedPrimitiveArray.h" namespace { constexpr int min(int a, int b) { return a < b ? a : b; } // Maximum number of bytes to write in one request. constexpr size_t MAX_WRITE = 256 * 1024; constexpr size_t NUM_MAX_HANDLES = 1024; // Largest possible request. // The request size is bounded by the maximum size of a FUSE_WRITE request Loading @@ -47,6 +45,8 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE; static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_get_object_bytes; struct FuseRequest { char buffer[MAX_REQUEST_SIZE]; Loading Loading @@ -79,15 +79,25 @@ public: * The class is used to access AppFuse class in Java from fuse handlers. */ class AppFuse { JNIEnv* env_; jobject self_; // Map between file handle and inode. std::map<uint32_t, uint64_t> handles_; uint32_t handle_counter_; public: AppFuse(JNIEnv* /*env*/, jobject /*self*/) { } AppFuse(JNIEnv* env, jobject self) : env_(env), self_(self), handle_counter_(0) {} bool handle_fuse_request(int fd, const FuseRequest& req) { ALOGV("Request op=%d", req.header().opcode); switch (req.header().opcode) { // TODO: Handle more operations that are enough to provide seekable // FD. case FUSE_LOOKUP: invoke_handler(fd, req, &AppFuse::handle_fuse_lookup); return true; case FUSE_INIT: invoke_handler(fd, req, &AppFuse::handle_fuse_init); return true; Loading @@ -96,6 +106,18 @@ public: return true; case FUSE_FORGET: return false; case FUSE_OPEN: invoke_handler(fd, req, &AppFuse::handle_fuse_open); return true; case FUSE_READ: invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192); return true; case FUSE_RELEASE: invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0); return true; case FUSE_FLUSH: invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0); return true; default: { ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n", req.header().opcode, Loading @@ -108,10 +130,37 @@ public: } private: int handle_fuse_lookup(const fuse_in_header& header, const char* name, fuse_entry_out* out, uint32_t* /*unused*/) { if (header.nodeid != 1) { return -ENOENT; } const int n = atoi(name); if (n == 0) { return -ENOENT; } int64_t size = get_file_size(n); if (size < 0) { return -ENOENT; } out->nodeid = n; out->attr_valid = 10; out->entry_valid = 10; out->attr.ino = n; out->attr.mode = S_IFREG | 0777; out->attr.size = size; return 0; } int handle_fuse_init(const fuse_in_header&, const fuse_init_in* in, fuse_init_out* out, size_t* reply_size) { uint32_t* reply_size) { // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out // defined (fuse version 7.6). The structure is the same from 7.6 through // 7.22. Beginning with 7.23, the structure increased in size and added Loading @@ -124,7 +173,7 @@ private: } // We limit ourselves to 15 because we don't handle BATCH_FORGET yet out->minor = min(in->minor, 15); out->minor = std::min(in->minor, 15u); #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) // FUSE_KERNEL_VERSION >= 23. Loading Loading @@ -152,7 +201,7 @@ private: int handle_fuse_getattr(const fuse_in_header& header, const fuse_getattr_in* /* in */, fuse_attr_out* out, size_t* /*unused*/) { uint32_t* /*unused*/) { if (header.nodeid != 1) { return -ENOENT; } Loading @@ -163,14 +212,67 @@ private: return 0; } int handle_fuse_open(const fuse_in_header& header, const fuse_open_in* /* in */, fuse_open_out* out, uint32_t* /*unused*/) { if (handles_.size() >= NUM_MAX_HANDLES) { // Too many open files. return -EMFILE; } uint32_t handle; do { handle = handle_counter_++; } while (handles_.count(handle) != 0); handles_.insert(std::make_pair(handle, header.nodeid)); out->fh = handle; return 0; } int handle_fuse_read(const fuse_in_header& /* header */, const fuse_read_in* in, void* out, uint32_t* reply_size) { const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); if (it == handles_.end()) { return -EBADF; } const int64_t result = get_object_bytes( it->second, in->offset, in->size, out); if (result < 0) { return -EIO; } *reply_size = static_cast<size_t>(result); return 0; } int handle_fuse_release(const fuse_in_header& /* header */, const fuse_release_in* in, void* /* out */, uint32_t* /* reply_size */) { handles_.erase(in->fh); return 0; } int handle_fuse_flush(const fuse_in_header& /* header */, const void* /* in */, void* /* out */, uint32_t* /* reply_size */) { return 0; } template <typename T, typename S> void invoke_handler(int fd, const FuseRequest& request, int (AppFuse::*handler)(const fuse_in_header&, const T*, S*, size_t*), size_t reply_size = sizeof(S)) { uint32_t*), uint32_t reply_size = sizeof(S)) { char reply_data[reply_size]; memset(reply_data, 0, reply_size); const int reply_code = (this->*handler)( Loading @@ -186,6 +288,39 @@ private: reply_size); } int64_t get_file_size(int inode) { return static_cast<int64_t>(env_->CallLongMethod( self_, app_fuse_get_file_size, static_cast<int>(inode))); } int64_t get_object_bytes( int inode, uint64_t offset, uint32_t size, void* buf) { const uint32_t read_size = static_cast<uint32_t>(std::min( static_cast<uint64_t>(size), get_file_size(inode) - offset)); const jbyteArray array = (jbyteArray) env_->CallObjectMethod( self_, app_fuse_get_object_bytes, inode, offset, read_size); if (array == nullptr) { return -1; } ScopedByteArrayRO bytes(env_, array); if (bytes.size() != read_size || bytes.get() == nullptr) { return -1; } memcpy(buf, bytes.get(), read_size); return read_size; } static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. Loading Loading @@ -219,6 +354,7 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop( ALOGD("Start fuse loop."); while (true) { FuseRequest request; const ssize_t result = TEMP_FAILURE_RETRY( read(fd, request.buffer, sizeof(request.buffer))); if (result < 0) { Loading Loading @@ -272,12 +408,27 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { ALOGE("Can't find com/android/mtp/AppFuse"); return -1; } app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz)); if (app_fuse_class == nullptr) { ALOGE("Can't obtain global reference for com/android/mtp/AppFuse"); return -1; } app_fuse_get_file_size = env->GetMethodID( app_fuse_class, "getFileSize", "(I)J"); if (app_fuse_get_file_size == nullptr) { ALOGE("Can't find getFileSize"); return -1; } app_fuse_get_object_bytes = env->GetMethodID( app_fuse_class, "getObjectBytes", "(IJI)[B"); if (app_fuse_get_object_bytes == nullptr) { ALOGE("Can't find getObjectBytes"); return -1; } const int result = android::AndroidRuntime::registerNativeMethods( env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods)); if (result < 0) { Loading packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +40 −5 Original line number Diff line number Diff line Loading @@ -17,16 +17,14 @@ package com.android.mtp; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import android.os.Process; /** * TODO: Remove VisibleForTesting class. */ Loading @@ -37,12 +35,14 @@ public class AppFuse { } private final String mName; private final Callback mCallback; private final Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; @VisibleForTesting AppFuse(String name) { AppFuse(String name, Callback callback) { mName = name; mCallback = callback; mMessageThread = new Thread(new Runnable() { @Override public void run() { Loading Loading @@ -72,10 +72,45 @@ public class AppFuse { } } /** * @param i * @throws FileNotFoundException */ @VisibleForTesting public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), ParcelFileDescriptor.MODE_READ_ONLY); } @VisibleForTesting File getMountPoint() { return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName); } static interface Callback { long getFileSize(int inode) throws FileNotFoundException; byte[] getObjectBytes(int inode, long offset, int size) throws IOException; } @VisibleForTesting private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); } catch (IOException e) { return -1; } } @VisibleForTesting private byte[] getObjectBytes(int inode, long offset, int size) { try { return mCallback.getObjectBytes(inode, offset, size); } catch (IOException e) { return null; } } private native boolean native_start_app_fuse_loop(int fd); } packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +94 −9 Original line number Diff line number Diff line Loading @@ -16,24 +16,27 @@ package com.android.mtp; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.MediumTest; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @SmallTest public class AppFuseTest extends AndroidTestCase { /** * TODO: Enable this test after adding SELinux policies for appfuse. * @throws ErrnoException * @throws InterruptedException */ public void disabled_testBasic() throws ErrnoException, InterruptedException { @MediumTest public class AppFuseTest extends AndroidTestCase { public void disabled_testMount() throws ErrnoException, InterruptedException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final AppFuse appFuse = new AppFuse("test"); final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); final File file = appFuse.getMountPoint(); assertTrue(file.isDirectory()); Loading @@ -41,4 +44,86 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); assertTrue(1 != Os.stat(file.getPath()).st_ino); } public void disabled_testOpenFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (INODE == inode) { return 1024; } throw new FileNotFoundException(); } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); fd.close(); appFuse.close(); } public void disabled_testOpenFile_error() { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); try { appFuse.openFile(INODE); fail(); } catch (Throwable t) { assertTrue(t instanceof FileNotFoundException); } appFuse.close(); } public void disabled_testReadFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (inode == INODE) { return BYTES.length; } return super.getFileSize(inode); } @Override public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { if (inode == INODE) { return Arrays.copyOfRange(BYTES, (int) offset, (int) offset + size); } return super.getObjectBytes(inode, offset, size); } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); try (final ParcelFileDescriptor.AutoCloseInputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { final byte[] buffer = new byte[1024]; final int size = stream.read(buffer, 0, buffer.length); assertEquals(5, size); } appFuse.close(); } private static class TestCallback implements AppFuse.Callback { @Override public long getFileSize(int inode) throws FileNotFoundException { throw new FileNotFoundException(); } @Override public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { throw new IOException(); } } } packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +5 −0 Original line number Diff line number Diff line Loading @@ -203,4 +203,9 @@ public class TestMtpManager extends MtpManager { } return Arrays.copyOf(result, count); } @Override byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException { return mImportFileBytes.get(pack(deviceId, objectHandle)); } } Loading
packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +162 −11 Original line number Diff line number Diff line Loading @@ -30,15 +30,13 @@ #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "nativehelper/ScopedPrimitiveArray.h" namespace { constexpr int min(int a, int b) { return a < b ? a : b; } // Maximum number of bytes to write in one request. constexpr size_t MAX_WRITE = 256 * 1024; constexpr size_t NUM_MAX_HANDLES = 1024; // Largest possible request. // The request size is bounded by the maximum size of a FUSE_WRITE request Loading @@ -47,6 +45,8 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE; static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_get_object_bytes; struct FuseRequest { char buffer[MAX_REQUEST_SIZE]; Loading Loading @@ -79,15 +79,25 @@ public: * The class is used to access AppFuse class in Java from fuse handlers. */ class AppFuse { JNIEnv* env_; jobject self_; // Map between file handle and inode. std::map<uint32_t, uint64_t> handles_; uint32_t handle_counter_; public: AppFuse(JNIEnv* /*env*/, jobject /*self*/) { } AppFuse(JNIEnv* env, jobject self) : env_(env), self_(self), handle_counter_(0) {} bool handle_fuse_request(int fd, const FuseRequest& req) { ALOGV("Request op=%d", req.header().opcode); switch (req.header().opcode) { // TODO: Handle more operations that are enough to provide seekable // FD. case FUSE_LOOKUP: invoke_handler(fd, req, &AppFuse::handle_fuse_lookup); return true; case FUSE_INIT: invoke_handler(fd, req, &AppFuse::handle_fuse_init); return true; Loading @@ -96,6 +106,18 @@ public: return true; case FUSE_FORGET: return false; case FUSE_OPEN: invoke_handler(fd, req, &AppFuse::handle_fuse_open); return true; case FUSE_READ: invoke_handler(fd, req, &AppFuse::handle_fuse_read, 8192); return true; case FUSE_RELEASE: invoke_handler(fd, req, &AppFuse::handle_fuse_release, 0); return true; case FUSE_FLUSH: invoke_handler(fd, req, &AppFuse::handle_fuse_flush, 0); return true; default: { ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n", req.header().opcode, Loading @@ -108,10 +130,37 @@ public: } private: int handle_fuse_lookup(const fuse_in_header& header, const char* name, fuse_entry_out* out, uint32_t* /*unused*/) { if (header.nodeid != 1) { return -ENOENT; } const int n = atoi(name); if (n == 0) { return -ENOENT; } int64_t size = get_file_size(n); if (size < 0) { return -ENOENT; } out->nodeid = n; out->attr_valid = 10; out->entry_valid = 10; out->attr.ino = n; out->attr.mode = S_IFREG | 0777; out->attr.size = size; return 0; } int handle_fuse_init(const fuse_in_header&, const fuse_init_in* in, fuse_init_out* out, size_t* reply_size) { uint32_t* reply_size) { // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out // defined (fuse version 7.6). The structure is the same from 7.6 through // 7.22. Beginning with 7.23, the structure increased in size and added Loading @@ -124,7 +173,7 @@ private: } // We limit ourselves to 15 because we don't handle BATCH_FORGET yet out->minor = min(in->minor, 15); out->minor = std::min(in->minor, 15u); #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) // FUSE_KERNEL_VERSION >= 23. Loading Loading @@ -152,7 +201,7 @@ private: int handle_fuse_getattr(const fuse_in_header& header, const fuse_getattr_in* /* in */, fuse_attr_out* out, size_t* /*unused*/) { uint32_t* /*unused*/) { if (header.nodeid != 1) { return -ENOENT; } Loading @@ -163,14 +212,67 @@ private: return 0; } int handle_fuse_open(const fuse_in_header& header, const fuse_open_in* /* in */, fuse_open_out* out, uint32_t* /*unused*/) { if (handles_.size() >= NUM_MAX_HANDLES) { // Too many open files. return -EMFILE; } uint32_t handle; do { handle = handle_counter_++; } while (handles_.count(handle) != 0); handles_.insert(std::make_pair(handle, header.nodeid)); out->fh = handle; return 0; } int handle_fuse_read(const fuse_in_header& /* header */, const fuse_read_in* in, void* out, uint32_t* reply_size) { const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); if (it == handles_.end()) { return -EBADF; } const int64_t result = get_object_bytes( it->second, in->offset, in->size, out); if (result < 0) { return -EIO; } *reply_size = static_cast<size_t>(result); return 0; } int handle_fuse_release(const fuse_in_header& /* header */, const fuse_release_in* in, void* /* out */, uint32_t* /* reply_size */) { handles_.erase(in->fh); return 0; } int handle_fuse_flush(const fuse_in_header& /* header */, const void* /* in */, void* /* out */, uint32_t* /* reply_size */) { return 0; } template <typename T, typename S> void invoke_handler(int fd, const FuseRequest& request, int (AppFuse::*handler)(const fuse_in_header&, const T*, S*, size_t*), size_t reply_size = sizeof(S)) { uint32_t*), uint32_t reply_size = sizeof(S)) { char reply_data[reply_size]; memset(reply_data, 0, reply_size); const int reply_code = (this->*handler)( Loading @@ -186,6 +288,39 @@ private: reply_size); } int64_t get_file_size(int inode) { return static_cast<int64_t>(env_->CallLongMethod( self_, app_fuse_get_file_size, static_cast<int>(inode))); } int64_t get_object_bytes( int inode, uint64_t offset, uint32_t size, void* buf) { const uint32_t read_size = static_cast<uint32_t>(std::min( static_cast<uint64_t>(size), get_file_size(inode) - offset)); const jbyteArray array = (jbyteArray) env_->CallObjectMethod( self_, app_fuse_get_object_bytes, inode, offset, read_size); if (array == nullptr) { return -1; } ScopedByteArrayRO bytes(env_, array); if (bytes.size() != read_size || bytes.get() == nullptr) { return -1; } memcpy(buf, bytes.get(), read_size); return read_size; } static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. Loading Loading @@ -219,6 +354,7 @@ jboolean com_android_mtp_AppFuse_start_app_fuse_loop( ALOGD("Start fuse loop."); while (true) { FuseRequest request; const ssize_t result = TEMP_FAILURE_RETRY( read(fd, request.buffer, sizeof(request.buffer))); if (result < 0) { Loading Loading @@ -272,12 +408,27 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { ALOGE("Can't find com/android/mtp/AppFuse"); return -1; } app_fuse_class = static_cast<jclass>(env->NewGlobalRef(clazz)); if (app_fuse_class == nullptr) { ALOGE("Can't obtain global reference for com/android/mtp/AppFuse"); return -1; } app_fuse_get_file_size = env->GetMethodID( app_fuse_class, "getFileSize", "(I)J"); if (app_fuse_get_file_size == nullptr) { ALOGE("Can't find getFileSize"); return -1; } app_fuse_get_object_bytes = env->GetMethodID( app_fuse_class, "getObjectBytes", "(IJI)[B"); if (app_fuse_get_object_bytes == nullptr) { ALOGE("Can't find getObjectBytes"); return -1; } const int result = android::AndroidRuntime::registerNativeMethods( env, "com/android/mtp/AppFuse", gMethods, NELEM(gMethods)); if (result < 0) { Loading
packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +40 −5 Original line number Diff line number Diff line Loading @@ -17,16 +17,14 @@ package com.android.mtp; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import android.os.Process; /** * TODO: Remove VisibleForTesting class. */ Loading @@ -37,12 +35,14 @@ public class AppFuse { } private final String mName; private final Callback mCallback; private final Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; @VisibleForTesting AppFuse(String name) { AppFuse(String name, Callback callback) { mName = name; mCallback = callback; mMessageThread = new Thread(new Runnable() { @Override public void run() { Loading Loading @@ -72,10 +72,45 @@ public class AppFuse { } } /** * @param i * @throws FileNotFoundException */ @VisibleForTesting public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), ParcelFileDescriptor.MODE_READ_ONLY); } @VisibleForTesting File getMountPoint() { return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName); } static interface Callback { long getFileSize(int inode) throws FileNotFoundException; byte[] getObjectBytes(int inode, long offset, int size) throws IOException; } @VisibleForTesting private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); } catch (IOException e) { return -1; } } @VisibleForTesting private byte[] getObjectBytes(int inode, long offset, int size) { try { return mCallback.getObjectBytes(inode, offset, size); } catch (IOException e) { return null; } } private native boolean native_start_app_fuse_loop(int fd); }
packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +94 −9 Original line number Diff line number Diff line Loading @@ -16,24 +16,27 @@ package com.android.mtp; import android.os.ParcelFileDescriptor; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.MediumTest; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @SmallTest public class AppFuseTest extends AndroidTestCase { /** * TODO: Enable this test after adding SELinux policies for appfuse. * @throws ErrnoException * @throws InterruptedException */ public void disabled_testBasic() throws ErrnoException, InterruptedException { @MediumTest public class AppFuseTest extends AndroidTestCase { public void disabled_testMount() throws ErrnoException, InterruptedException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final AppFuse appFuse = new AppFuse("test"); final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); final File file = appFuse.getMountPoint(); assertTrue(file.isDirectory()); Loading @@ -41,4 +44,86 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); assertTrue(1 != Os.stat(file.getPath()).st_ino); } public void disabled_testOpenFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (INODE == inode) { return 1024; } throw new FileNotFoundException(); } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); fd.close(); appFuse.close(); } public void disabled_testOpenFile_error() { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); try { appFuse.openFile(INODE); fail(); } catch (Throwable t) { assertTrue(t instanceof FileNotFoundException); } appFuse.close(); } public void disabled_testReadFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (inode == INODE) { return BYTES.length; } return super.getFileSize(inode); } @Override public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { if (inode == INODE) { return Arrays.copyOfRange(BYTES, (int) offset, (int) offset + size); } return super.getObjectBytes(inode, offset, size); } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); try (final ParcelFileDescriptor.AutoCloseInputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { final byte[] buffer = new byte[1024]; final int size = stream.read(buffer, 0, buffer.length); assertEquals(5, size); } appFuse.close(); } private static class TestCallback implements AppFuse.Callback { @Override public long getFileSize(int inode) throws FileNotFoundException { throw new FileNotFoundException(); } @Override public byte[] getObjectBytes(int inode, long offset, int size) throws IOException { throw new IOException(); } } }
packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +5 −0 Original line number Diff line number Diff line Loading @@ -203,4 +203,9 @@ public class TestMtpManager extends MtpManager { } return Arrays.copyOf(result, count); } @Override byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException { return mImportFileBytes.get(pack(deviceId, objectHandle)); } }