Loading packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +70 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_write_object_bytes; static jfieldID app_fuse_buffer; // NOTE: Loading Loading @@ -140,6 +141,9 @@ public: case FUSE_READ: invoke_handler(fd, req, &AppFuse::handle_fuse_read); return true; case FUSE_WRITE: invoke_handler(fd, req, &AppFuse::handle_fuse_write); return true; case FUSE_RELEASE: invoke_handler(fd, req, &AppFuse::handle_fuse_release); return true; Loading Loading @@ -289,6 +293,29 @@ private: return 0; } int handle_fuse_write(const fuse_in_header& /* header */, const fuse_write_in* in, FuseResponse<fuse_write_out>* out) { if (in->size > MAX_WRITE) { return -EINVAL; } const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); if (it == handles_.end()) { return -EBADF; } const uint64_t offset = in->offset; const uint32_t size = in->size; const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in); uint32_t written_size; const int result = write_object_bytes(it->second, offset, size, buffer, &written_size); if (result < 0) { return result; } out->prepare_buffer(); out->data()->size = written_size; return 0; } int handle_fuse_release(const fuse_in_header& /* header */, const fuse_release_in* in, FuseResponse<void>* /* out */) { Loading Loading @@ -355,6 +382,27 @@ private: return read_size; } int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { ScopedLocalRef<jbyteArray> array( env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer))); { ScopedByteArrayRW bytes(env_, array.get()); if (bytes.get() == nullptr) { return -EIO; } memcpy(bytes.get(), buffer, size); } *written_size = env_->CallIntMethod( self_, app_fuse_write_object_bytes, inode, offset, size, array.get()); if (env_->ExceptionCheck()) { env_->ExceptionClear(); return -EIO; } return 0; } 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 @@ -469,6 +517,28 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { return -1; } app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find getWriteObjectBytes"); return -1; } app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B"); if (app_fuse_buffer == nullptr) { ALOGE("Can't find mBuffer"); return -1; } const jfieldID read_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_READ", "I"); if (static_cast<int>(env->GetStaticIntField(app_fuse_class, read_max_fied)) != MAX_READ) { return -1; } const jfieldID write_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_WRITE", "I"); if (static_cast<int>(env->GetStaticIntField(app_fuse_class, write_max_fied)) != MAX_WRITE) { 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 +39 −4 Original line number Diff line number Diff line Loading @@ -38,8 +38,12 @@ public class AppFuse { * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. */ @UsedByNative("com_android_mtp_AppFuse.cpp") static final int MAX_READ = 128 * 1024; @UsedByNative("com_android_mtp_AppFuse.cpp") static final int MAX_WRITE = 256 * 1024; private final String mName; private final Callback mCallback; Loading @@ -47,7 +51,7 @@ public class AppFuse { * Buffer for read bytes request. * Don't use the buffer from the out of AppFuseMessageThread. */ private byte[] mBuffer = new byte[MAX_READ]; private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)]; private Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; Loading Loading @@ -79,11 +83,22 @@ public class AppFuse { } } public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { /** * Opens a file on app fuse and returns ParcelFileDescriptor. * * @param i ID for opened file. * @param mode Mode for opening file. * @see ParcelFileDescriptor#MODE_READ_ONLY * @see ParcelFileDescriptor#MODE_WRITE_ONLY */ public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { Preconditions.checkArgument( mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == ParcelFileDescriptor.MODE_WRITE_ONLY); return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), ParcelFileDescriptor.MODE_READ_ONLY); mode); } File getMountPoint() { Loading @@ -100,7 +115,7 @@ public class AppFuse { long getFileSize(int inode) throws FileNotFoundException; /** * Returns flie bytes for the give inode. * Returns file bytes for the give inode. * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. Loading @@ -109,6 +124,17 @@ public class AppFuse { * @throws IOException */ long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException; /** * Handles writing bytes for the give inode. * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. * @param bytes Buffer to store file bytes. * @return Number of read bytes. Must not be negative. * @throws IOException */ int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException; } @UsedByNative("com_android_mtp_AppFuse.cpp") Loading Loading @@ -138,6 +164,15 @@ public class AppFuse { } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private /* unsgined */ int writeObjectBytes(int inode, /* unsigned */ long offset, /* unsigned */ int size, byte[] bytes) throws IOException { return mCallback.writeObjectBytes(inode, offset, size, bytes); } private native boolean native_start_app_fuse_loop(int fd); private class AppFuseMessageThread extends Thread { Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +9 −1 Original line number Diff line number Diff line Loading @@ -242,7 +242,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { // extension. if (MtpDeviceRecord.isPartialReadSupported( device.operationsSupported, fileSize)) { return mAppFuse.openFile(Integer.parseInt(documentId)); return mAppFuse.openFile( Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY); } else { return getPipeManager(identifier).readDocument(mMtpManager, identifier); } Loading Loading @@ -606,5 +607,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { // TODO: Implement it. throw new IOException(); } } } packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java +1 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import java.lang.annotation.Target; /** * Annotation that shows the method is used by JNI. */ @Target(ElementType.METHOD) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface UsedByNative { /** * JNI file name that uses the method. Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +89 −6 Original line number Diff line number Diff line Loading @@ -56,7 +56,8 @@ public class AppFuseTest extends AndroidTestCase { } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_READ_ONLY); fd.close(); appFuse.close(); } Loading @@ -67,11 +68,21 @@ public class AppFuseTest extends AndroidTestCase { final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); try { appFuse.openFile(INODE); appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_ONLY); fail(); } catch (Throwable t) { assertTrue(t instanceof FileNotFoundException); } catch (FileNotFoundException exp) {} appFuse.close(); } public void testOpenFile_illegalMode() throws IOException { 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, ParcelFileDescriptor.MODE_READ_WRITE); fail(); } catch (IllegalArgumentException exp) {} appFuse.close(); } Loading Loading @@ -105,7 +116,8 @@ public class AppFuseTest extends AndroidTestCase { } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(fileInode); final ParcelFileDescriptor fd = appFuse.openFile( fileInode, ParcelFileDescriptor.MODE_READ_ONLY); try (final ParcelFileDescriptor.AutoCloseInputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { final byte[] buffer = new byte[1024]; Loading @@ -115,6 +127,71 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); } public void testWriteFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final byte[] resultBytes = new byte[5]; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (inode != INODE) { throw new FileNotFoundException(); } return resultBytes.length; } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) { for (int i = 0; i < size; i++) { resultBytes[(int)(offset + i)] = bytes[i]; } return size; } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); stream.write('b'); stream.write('c'); stream.write('d'); stream.write('e'); } final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; assertTrue(Arrays.equals(BYTES, resultBytes)); appFuse.close(); } public void testWriteFile_writeError() 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) { throw new FileNotFoundException(); } return 5; } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); fail(); } catch (IOException e) { } appFuse.close(); } private static class TestCallback implements AppFuse.Callback { @Override public long getFileSize(int inode) throws FileNotFoundException { Loading @@ -126,5 +203,11 @@ public class AppFuseTest extends AndroidTestCase { throws IOException { throw new IOException(); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { throw new IOException(); } } } Loading
packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +70 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) + static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_write_object_bytes; static jfieldID app_fuse_buffer; // NOTE: Loading Loading @@ -140,6 +141,9 @@ public: case FUSE_READ: invoke_handler(fd, req, &AppFuse::handle_fuse_read); return true; case FUSE_WRITE: invoke_handler(fd, req, &AppFuse::handle_fuse_write); return true; case FUSE_RELEASE: invoke_handler(fd, req, &AppFuse::handle_fuse_release); return true; Loading Loading @@ -289,6 +293,29 @@ private: return 0; } int handle_fuse_write(const fuse_in_header& /* header */, const fuse_write_in* in, FuseResponse<fuse_write_out>* out) { if (in->size > MAX_WRITE) { return -EINVAL; } const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh); if (it == handles_.end()) { return -EBADF; } const uint64_t offset = in->offset; const uint32_t size = in->size; const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in); uint32_t written_size; const int result = write_object_bytes(it->second, offset, size, buffer, &written_size); if (result < 0) { return result; } out->prepare_buffer(); out->data()->size = written_size; return 0; } int handle_fuse_release(const fuse_in_header& /* header */, const fuse_release_in* in, FuseResponse<void>* /* out */) { Loading Loading @@ -355,6 +382,27 @@ private: return read_size; } int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { ScopedLocalRef<jbyteArray> array( env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer))); { ScopedByteArrayRW bytes(env_, array.get()); if (bytes.get() == nullptr) { return -EIO; } memcpy(bytes.get(), buffer, size); } *written_size = env_->CallIntMethod( self_, app_fuse_write_object_bytes, inode, offset, size, array.get()); if (env_->ExceptionCheck()) { env_->ExceptionClear(); return -EIO; } return 0; } 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 @@ -469,6 +517,28 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { return -1; } app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find getWriteObjectBytes"); return -1; } app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B"); if (app_fuse_buffer == nullptr) { ALOGE("Can't find mBuffer"); return -1; } const jfieldID read_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_READ", "I"); if (static_cast<int>(env->GetStaticIntField(app_fuse_class, read_max_fied)) != MAX_READ) { return -1; } const jfieldID write_max_fied = env->GetStaticFieldID(app_fuse_class, "MAX_WRITE", "I"); if (static_cast<int>(env->GetStaticIntField(app_fuse_class, write_max_fied)) != MAX_WRITE) { 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 +39 −4 Original line number Diff line number Diff line Loading @@ -38,8 +38,12 @@ public class AppFuse { * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. */ @UsedByNative("com_android_mtp_AppFuse.cpp") static final int MAX_READ = 128 * 1024; @UsedByNative("com_android_mtp_AppFuse.cpp") static final int MAX_WRITE = 256 * 1024; private final String mName; private final Callback mCallback; Loading @@ -47,7 +51,7 @@ public class AppFuse { * Buffer for read bytes request. * Don't use the buffer from the out of AppFuseMessageThread. */ private byte[] mBuffer = new byte[MAX_READ]; private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)]; private Thread mMessageThread; private ParcelFileDescriptor mDeviceFd; Loading Loading @@ -79,11 +83,22 @@ public class AppFuse { } } public ParcelFileDescriptor openFile(int i) throws FileNotFoundException { /** * Opens a file on app fuse and returns ParcelFileDescriptor. * * @param i ID for opened file. * @param mode Mode for opening file. * @see ParcelFileDescriptor#MODE_READ_ONLY * @see ParcelFileDescriptor#MODE_WRITE_ONLY */ public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { Preconditions.checkArgument( mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == ParcelFileDescriptor.MODE_WRITE_ONLY); return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), ParcelFileDescriptor.MODE_READ_ONLY); mode); } File getMountPoint() { Loading @@ -100,7 +115,7 @@ public class AppFuse { long getFileSize(int inode) throws FileNotFoundException; /** * Returns flie bytes for the give inode. * Returns file bytes for the give inode. * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. Loading @@ -109,6 +124,17 @@ public class AppFuse { * @throws IOException */ long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException; /** * Handles writing bytes for the give inode. * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. * @param bytes Buffer to store file bytes. * @return Number of read bytes. Must not be negative. * @throws IOException */ int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException; } @UsedByNative("com_android_mtp_AppFuse.cpp") Loading Loading @@ -138,6 +164,15 @@ public class AppFuse { } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private /* unsgined */ int writeObjectBytes(int inode, /* unsigned */ long offset, /* unsigned */ int size, byte[] bytes) throws IOException { return mCallback.writeObjectBytes(inode, offset, size, bytes); } private native boolean native_start_app_fuse_loop(int fd); private class AppFuseMessageThread extends Thread { Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +9 −1 Original line number Diff line number Diff line Loading @@ -242,7 +242,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { // extension. if (MtpDeviceRecord.isPartialReadSupported( device.operationsSupported, fileSize)) { return mAppFuse.openFile(Integer.parseInt(documentId)); return mAppFuse.openFile( Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY); } else { return getPipeManager(identifier).readDocument(mMtpManager, identifier); } Loading Loading @@ -606,5 +607,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { // TODO: Implement it. throw new IOException(); } } }
packages/MtpDocumentsProvider/src/com/android/mtp/annotations/UsedByNative.java +1 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import java.lang.annotation.Target; /** * Annotation that shows the method is used by JNI. */ @Target(ElementType.METHOD) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface UsedByNative { /** * JNI file name that uses the method. Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java +89 −6 Original line number Diff line number Diff line Loading @@ -56,7 +56,8 @@ public class AppFuseTest extends AndroidTestCase { } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(INODE); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_READ_ONLY); fd.close(); appFuse.close(); } Loading @@ -67,11 +68,21 @@ public class AppFuseTest extends AndroidTestCase { final AppFuse appFuse = new AppFuse("test", new TestCallback()); appFuse.mount(storageManager); try { appFuse.openFile(INODE); appFuse.openFile(INODE, ParcelFileDescriptor.MODE_READ_ONLY); fail(); } catch (Throwable t) { assertTrue(t instanceof FileNotFoundException); } catch (FileNotFoundException exp) {} appFuse.close(); } public void testOpenFile_illegalMode() throws IOException { 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, ParcelFileDescriptor.MODE_READ_WRITE); fail(); } catch (IllegalArgumentException exp) {} appFuse.close(); } Loading Loading @@ -105,7 +116,8 @@ public class AppFuseTest extends AndroidTestCase { } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile(fileInode); final ParcelFileDescriptor fd = appFuse.openFile( fileInode, ParcelFileDescriptor.MODE_READ_ONLY); try (final ParcelFileDescriptor.AutoCloseInputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(fd)) { final byte[] buffer = new byte[1024]; Loading @@ -115,6 +127,71 @@ public class AppFuseTest extends AndroidTestCase { appFuse.close(); } public void testWriteFile() throws IOException { final StorageManager storageManager = getContext().getSystemService(StorageManager.class); final int INODE = 10; final byte[] resultBytes = new byte[5]; final AppFuse appFuse = new AppFuse( "test", new TestCallback() { @Override public long getFileSize(int inode) throws FileNotFoundException { if (inode != INODE) { throw new FileNotFoundException(); } return resultBytes.length; } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) { for (int i = 0; i < size; i++) { resultBytes[(int)(offset + i)] = bytes[i]; } return size; } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); stream.write('b'); stream.write('c'); stream.write('d'); stream.write('e'); } final byte[] BYTES = new byte[] { 'a', 'b', 'c', 'd', 'e' }; assertTrue(Arrays.equals(BYTES, resultBytes)); appFuse.close(); } public void testWriteFile_writeError() 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) { throw new FileNotFoundException(); } return 5; } }); appFuse.mount(storageManager); final ParcelFileDescriptor fd = appFuse.openFile( INODE, ParcelFileDescriptor.MODE_WRITE_ONLY); try (final ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write('a'); fail(); } catch (IOException e) { } appFuse.close(); } private static class TestCallback implements AppFuse.Callback { @Override public long getFileSize(int inode) throws FileNotFoundException { Loading @@ -126,5 +203,11 @@ public class AppFuseTest extends AndroidTestCase { throws IOException { throw new IOException(); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { throw new IOException(); } } }