Use public version of AppFuse in MtpDocumentsProvider.

AppFuse is now a public API. The CL removes the private version from
MtpDocuemntsProvider package and switches to the public version.

Bug: 32891253
Test: MtpDocumentsProviderTest
Change-Id: Ibdf67309bc0678e2f70ac2dddb920125d9e0760e
diff --git a/Android.mk b/Android.mk
index c9e7195..a9e9b2e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,7 +6,6 @@
 LOCAL_PACKAGE_NAME := MtpDocumentsProvider
 LOCAL_CERTIFICATE := media
 LOCAL_PRIVILEGED_MODULE := true
-LOCAL_JNI_SHARED_LIBRARIES := libappfuse_jni
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 # Only enable asserts on userdebug/eng builds
diff --git a/jni/Android.mk b/jni/Android.mk
deleted file mode 100644
index d545b14..0000000
--- a/jni/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-    com_android_mtp_AppFuse.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libandroid_runtime \
-    libnativehelper \
-    liblog
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-LOCAL_MODULE := libappfuse_jni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/com_android_mtp_AppFuse.cpp b/jni/com_android_mtp_AppFuse.cpp
deleted file mode 100644
index e948cf7..0000000
--- a/jni/com_android_mtp_AppFuse.cpp
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright (C) 2015 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 specic language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AppFuseJNI"
-#include "utils/Log.h"
-
-#include <assert.h>
-#include <dirent.h>
-#include <inttypes.h>
-
-#include <linux/fuse.h>
-#include <sys/stat.h>
-#include <sys/uio.h>
-
-#include <map>
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "nativehelper/ScopedPrimitiveArray.h"
-#include "nativehelper/ScopedLocalRef.h"
-
-namespace {
-
-// The numbers came from sdcard.c.
-// Maximum number of bytes to write/read in one request/one reply.
-constexpr size_t MAX_WRITE = 256 * 1024;
-constexpr size_t MAX_READ = 128 * 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
-// because it has the largest possible data payload.
-constexpr size_t MAX_REQUEST_SIZE = sizeof(struct fuse_in_header) +
-        sizeof(struct fuse_write_in) + (MAX_WRITE > MAX_READ ? MAX_WRITE : MAX_READ);
-
-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 jmethodID app_fuse_flush_file_handle;
-static jmethodID app_fuse_close_file_handle;
-static jfieldID app_fuse_buffer;
-
-// NOTE:
-// FuseRequest and FuseResponse shares the same buffer to save memory usage, so the handlers must
-// not access input buffer after writing data to output buffer.
-struct FuseRequest {
-    char buffer[MAX_REQUEST_SIZE];
-    FuseRequest() {}
-    const struct fuse_in_header& header() const {
-        return *(const struct fuse_in_header*) buffer;
-    }
-    void* data() {
-        return (buffer + sizeof(struct fuse_in_header));
-    }
-    size_t data_length() const {
-        return header().len - sizeof(struct fuse_in_header);
-    }
-};
-
-template<typename T>
-class FuseResponse {
-   size_t size_;
-   T* const buffer_;
-public:
-   explicit FuseResponse(void* buffer) : size_(0), buffer_(static_cast<T*>(buffer)) {}
-
-   void prepare_buffer(size_t size = sizeof(T)) {
-       memset(buffer_, 0, size);
-       size_ = size;
-   }
-
-   void set_size(size_t size) {
-       size_ = size;
-   }
-
-   size_t size() const { return size_; }
-   T* data() const { return buffer_; }
-};
-
-class ScopedFd {
-    int mFd;
-
-public:
-    explicit ScopedFd(int fd) : mFd(fd) {}
-    ~ScopedFd() {
-        close(mFd);
-    }
-    operator int() {
-        return mFd;
-    }
-};
-
-/**
- * Fuse implementation consists of handlers parsing FUSE commands.
- */
-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) :
-        env_(env), self_(self), handle_counter_(0) {}
-
-    void handle_fuse_request(int fd, 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;
-            case FUSE_FORGET:
-                // Return without replying.
-                return;
-            case FUSE_INIT:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_init);
-                return;
-            case FUSE_GETATTR:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_getattr);
-                return;
-            case FUSE_OPEN:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_open);
-                return;
-            case FUSE_READ:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_read);
-                return;
-            case FUSE_WRITE:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_write);
-                return;
-            case FUSE_RELEASE:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_release);
-                return;
-            case FUSE_FLUSH:
-                invoke_handler(fd, req, &AppFuse::handle_fuse_flush);
-                return;
-            default: {
-                ALOGV("NOTIMPL op=%d uniq=%" PRIx64 " nid=%" PRIx64 "\n",
-                      req->header().opcode,
-                      req->header().unique,
-                      req->header().nodeid);
-                fuse_reply(fd, req->header().unique, -ENOSYS, NULL, 0);
-                return;
-            }
-        }
-    }
-
-private:
-    int handle_fuse_lookup(const fuse_in_header& header,
-                           const char* name,
-                           FuseResponse<fuse_entry_out>* out) {
-        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->prepare_buffer();
-        out->data()->nodeid = n;
-        out->data()->attr_valid = 10;
-        out->data()->entry_valid = 10;
-        out->data()->attr.ino = n;
-        out->data()->attr.mode = S_IFREG | 0777;
-        out->data()->attr.size = size;
-        return 0;
-    }
-
-    int handle_fuse_init(const fuse_in_header&,
-                         const fuse_init_in* in,
-                         FuseResponse<fuse_init_out>* out) {
-        // 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
-        // new parameters.
-        if (in->major != FUSE_KERNEL_VERSION || in->minor < 6) {
-            ALOGE("Fuse kernel version mismatch: Kernel version %d.%d, "
-                  "Expected at least %d.6",
-                  in->major, in->minor, FUSE_KERNEL_VERSION);
-            return -1;
-        }
-
-        // Before writing |out|, we need to copy data from |in|.
-        const uint32_t minor = in->minor;
-        const uint32_t max_readahead = in->max_readahead;
-
-        // We limit ourselves to 15 because we don't handle BATCH_FORGET yet
-        size_t response_size = sizeof(fuse_init_out);
-#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE)
-        // FUSE_KERNEL_VERSION >= 23.
-
-        // If the kernel only works on minor revs older than or equal to 22,
-        // then use the older structure size since this code only uses the 7.22
-        // version of the structure.
-        if (minor <= 22) {
-            response_size = FUSE_COMPAT_22_INIT_OUT_SIZE;
-        }
-#endif
-        out->prepare_buffer(response_size);
-        out->data()->major = FUSE_KERNEL_VERSION;
-        out->data()->minor = std::min(minor, 15u);
-        out->data()->max_readahead = max_readahead;
-        out->data()->flags = FUSE_ATOMIC_O_TRUNC | FUSE_BIG_WRITES;
-        out->data()->max_background = 32;
-        out->data()->congestion_threshold = 32;
-        out->data()->max_write = MAX_WRITE;
-
-        return 0;
-    }
-
-    int handle_fuse_getattr(const fuse_in_header& header,
-                            const fuse_getattr_in* /* in */,
-                            FuseResponse<fuse_attr_out>* out) {
-        out->prepare_buffer();
-        out->data()->attr_valid = 10;
-        out->data()->attr.ino = header.nodeid;
-        if (header.nodeid == 1) {
-            out->data()->attr.mode = S_IFDIR | 0777;
-            out->data()->attr.size = 0;
-        } else {
-            int64_t size = get_file_size(header.nodeid);
-            if (size < 0) {
-                return -ENOENT;
-            }
-            out->data()->attr.mode = S_IFREG | 0777;
-            out->data()->attr.size = size;
-        }
-
-        return 0;
-    }
-
-    int handle_fuse_open(const fuse_in_header& header,
-                         const fuse_open_in* /* in */,
-                         FuseResponse<fuse_open_out>* out) {
-        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->prepare_buffer();
-        out->data()->fh = handle;
-        return 0;
-    }
-
-    int handle_fuse_read(const fuse_in_header& /* header */,
-                         const fuse_read_in* in,
-                         FuseResponse<void>* out) {
-        if (in->size > MAX_READ) {
-            return -EINVAL;
-        }
-        const std::map<uint32_t, uint64_t>::iterator it = handles_.find(in->fh);
-        if (it == handles_.end()) {
-            return -EBADF;
-        }
-        uint64_t offset = in->offset;
-        uint32_t size = in->size;
-
-        // Overwrite the size after writing data.
-        out->prepare_buffer(0);
-        const int64_t result = get_object_bytes(it->second, offset, size, out->data());
-        if (result < 0) {
-            return result;
-        }
-        out->set_size(result);
-        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(
-                in->fh, 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 */) {
-        handles_.erase(in->fh);
-        return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh));
-    }
-
-    int handle_fuse_flush(const fuse_in_header& /* header */,
-                          const fuse_flush_in* in,
-                          FuseResponse<void>* /* out */) {
-        return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh));
-    }
-
-    template <typename T, typename S>
-    void invoke_handler(int fd,
-                        FuseRequest* request,
-                        int (AppFuse::*handler)(const fuse_in_header&,
-                                                const T*,
-                                                FuseResponse<S>*)) {
-        FuseResponse<S> response(request->data());
-        const int reply_code = (this->*handler)(
-                request->header(),
-                static_cast<const T*>(request->data()),
-                &response);
-        fuse_reply(
-                fd,
-                request->header().unique,
-                reply_code,
-                request->data(),
-                response.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 jlong read_size = env_->CallLongMethod(
-                self_,
-                app_fuse_read_object_bytes,
-                static_cast<jint>(inode),
-                static_cast<jlong>(offset),
-                static_cast<jlong>(size));
-        if (read_size <= 0) {
-            return read_size;
-        }
-        ScopedLocalRef<jbyteArray> array(
-                env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer)));
-        if (array.get() == nullptr) {
-            return -EFAULT;
-        }
-        ScopedByteArrayRO bytes(env_, array.get());
-        if (bytes.get() == nullptr) {
-            return -ENOMEM;
-        }
-        memcpy(buf, bytes.get(), read_size);
-        return read_size;
-    }
-
-    int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size,
-                           const void* buffer, uint32_t* written_size) {
-        static_assert(sizeof(uint64_t) <= sizeof(jlong),
-                      "jlong must be able to express any uint64_t values");
-        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);
-        }
-        const int result = env_->CallIntMethod(
-                self_,
-                app_fuse_write_object_bytes,
-                file_handle_to_jlong(handle),
-                inode,
-                offset,
-                size,
-                array.get());
-        if (result < 0) {
-            return result;
-        }
-        *written_size = result;
-        return 0;
-    }
-
-    static jlong file_handle_to_jlong(uint64_t handle) {
-        static_assert(
-                sizeof(uint64_t) <= sizeof(jlong),
-                "jlong must be able to express any uint64_t values");
-        return static_cast<jlong>(handle);
-    }
-
-    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.
-        if (reply_code != 0) {
-            reply_size = 0;
-        }
-
-        struct fuse_out_header hdr;
-        hdr.len = reply_size + sizeof(hdr);
-        hdr.error = reply_code;
-        hdr.unique = unique;
-
-        struct iovec vec[2];
-        vec[0].iov_base = &hdr;
-        vec[0].iov_len = sizeof(hdr);
-        vec[1].iov_base = reply_data;
-        vec[1].iov_len = reply_size;
-
-        const int res = writev(fd, vec, reply_size != 0 ? 2 : 1);
-        if (res < 0) {
-            ALOGE("*** REPLY FAILED *** %d\n", errno);
-        }
-    }
-};
-
-void com_android_mtp_AppFuse_start_app_fuse_loop(JNIEnv* env, jobject self, jint jfd) {
-    ScopedFd fd(static_cast<int>(jfd));
-    AppFuse appfuse(env, self);
-
-    ALOGV("Start fuse loop.");
-    while (true) {
-        FuseRequest request;
-
-        const ssize_t result = TEMP_FAILURE_RETRY(
-                read(fd, request.buffer, sizeof(request.buffer)));
-        if (result < 0) {
-            if (errno == ENODEV) {
-                ALOGV("AppFuse was unmounted.\n");
-                return;
-            }
-            ALOGE("Failed to read bytes from FD: errno=%d\n", errno);
-            continue;
-        }
-
-        const size_t length = static_cast<size_t>(result);
-        if (length < sizeof(struct fuse_in_header)) {
-            ALOGE("request too short: len=%zu\n", length);
-            continue;
-        }
-
-        if (request.header().len != length) {
-            ALOGE("malformed header: len=%zu, hdr->len=%u\n",
-                  length, request.header().len);
-            continue;
-        }
-
-        appfuse.handle_fuse_request(fd, &request);
-    }
-}
-
-static const JNINativeMethod gMethods[] = {
-    {
-        "native_start_app_fuse_loop",
-        "(I)V",
-        (void *) com_android_mtp_AppFuse_start_app_fuse_loop
-    }
-};
-
-}
-
-jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
-    JNIEnv* env = nullptr;
-    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGE("ERROR: GetEnv failed\n");
-        return -1;
-
-    }
-    assert(env != nullptr);
-
-    jclass clazz = env->FindClass("com/android/mtp/AppFuse");
-    if (clazz == nullptr) {
-        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_read_object_bytes = env->GetMethodID(
-            app_fuse_class, "readObjectBytes", "(IJJ)J");
-    if (app_fuse_read_object_bytes == nullptr) {
-        ALOGE("Can't find readObjectBytes");
-        return -1;
-    }
-
-    app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I");
-    if (app_fuse_write_object_bytes == nullptr) {
-        ALOGE("Can't find writeObjectBytes");
-        return -1;
-    }
-
-    app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I");
-    if (app_fuse_flush_file_handle == nullptr) {
-        ALOGE("Can't find flushFileHandle");
-        return -1;
-    }
-
-    app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I");
-    if (app_fuse_close_file_handle == nullptr) {
-        ALOGE("Can't find closeFileHandle");
-        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) {
-        return -1;
-    }
-
-    return JNI_VERSION_1_4;
-}
diff --git a/perf_tests/src/com/android/mtp/AppFusePerfTest.java b/perf_tests/src/com/android/mtp/AppFusePerfTest.java
index 0762571..36f6fe9 100644
--- a/perf_tests/src/com/android/mtp/AppFusePerfTest.java
+++ b/perf_tests/src/com/android/mtp/AppFusePerfTest.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
 import android.os.storage.StorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -36,38 +37,13 @@
 
 @RunWith(JUnit4.class)
 public class AppFusePerfTest {
+    final static int SIZE = 10 * 1024 * 1024;  // 10MB
+
     @Test
     @LargeTest
     public void testReadWriteFile() throws IOException {
         final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
         final StorageManager storageManager = context.getSystemService(StorageManager.class);
-        final int INODE = 10;
-        final int SIZE = 10 * 1024 * 1024;  // 10MB
-        final AppFuse appFuse = new AppFuse(
-                "test",
-                new TestCallback() {
-                    @Override
-                    public long getFileSize(int inode) throws FileNotFoundException {
-                        if (inode != INODE) {
-                            throw new FileNotFoundException();
-                        }
-                        return SIZE;
-                    }
-
-                    @Override
-                    public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
-                            throws IOException {
-                        return size;
-                    }
-
-                    @Override
-                    public int writeObjectBytes(
-                            long fileHandle, int inode, long offset, int size, byte[] bytes) {
-                        return size;
-                    }
-                });
-
-        appFuse.mount(storageManager);
 
         final byte[] bytes = new byte[SIZE];
         final int SAMPLES = 100;
@@ -75,22 +51,20 @@
         final double[] writeTime = new double[SAMPLES];
 
         for (int i = 0; i < SAMPLES; i++) {
-            final ParcelFileDescriptor fd = appFuse.openFile(
-                    INODE,
-                    ParcelFileDescriptor.MODE_READ_ONLY);
+            final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
+                    ParcelFileDescriptor.MODE_READ_ONLY, new TestCallback());
             try (final ParcelFileDescriptor.AutoCloseInputStream stream =
                     new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
                 final long startTime = System.nanoTime();
                 stream.read(bytes);
                 readTime[i] = (System.nanoTime() - startTime) / 1000.0 / 1000.0;
             }
-
         }
 
         for (int i = 0; i < SAMPLES; i++) {
-            final ParcelFileDescriptor fd = appFuse.openFile(
-                    INODE,
-                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
+            final ParcelFileDescriptor fd = storageManager.openProxyFileDescriptor(
+                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE,
+                    new TestCallback());
             try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
                     new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
                 final long startTime = System.nanoTime();
@@ -99,8 +73,6 @@
             }
         }
 
-        appFuse.close();
-
         double readAverage = 0;
         double writeAverage = 0;
         double readSquaredAverage = 0;
@@ -127,28 +99,26 @@
         InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, results);
     }
 
-    private static class TestCallback implements AppFuse.Callback {
+    private static class TestCallback extends ProxyFileDescriptorCallback {
         @Override
-        public long getFileSize(int inode) throws FileNotFoundException {
-            throw new FileNotFoundException();
+        public long onGetSize() throws ErrnoException {
+            return SIZE;
         }
 
         @Override
-        public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
-                throws IOException {
-            throw new IOException();
+        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+            return size;
         }
 
         @Override
-        public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
-                throws IOException {
-            throw new IOException();
+        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+            return size;
         }
 
         @Override
-        public void flushFileHandle(long fileHandle) throws IOException {}
+        public void onFsync() throws ErrnoException {}
 
         @Override
-        public void closeFileHandle(long fileHandle) {}
+        public void onRelease() {}
     }
 }
diff --git a/src/com/android/mtp/AppFuse.java b/src/com/android/mtp/AppFuse.java
deleted file mode 100644
index cd78e61..0000000
--- a/src/com/android/mtp/AppFuse.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-import android.annotation.WorkerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.mtp.annotations.UsedByNative;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-public class AppFuse {
-    static {
-        System.loadLibrary("appfuse_jni");
-    }
-
-    private static final boolean DEBUG = false;
-
-    /**
-     * 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;
-
-    /**
-     * Buffer for read bytes request.
-     * Don't use the buffer from the out of AppFuseMessageThread.
-     */
-    private byte[] mBuffer = new byte[Math.max(MAX_READ, MAX_WRITE)];
-
-    private Thread mMessageThread;
-    private ParcelFileDescriptor mDeviceFd;
-
-    AppFuse(String name, Callback callback) {
-        mName = name;
-        mCallback = callback;
-    }
-
-    void mount(StorageManager storageManager) throws IOException {
-        Preconditions.checkState(mDeviceFd == null);
-        mDeviceFd = storageManager.mountAppFuse(mName);
-        mMessageThread = new AppFuseMessageThread(mDeviceFd.dup().detachFd());
-        mMessageThread.start();
-    }
-
-    @VisibleForTesting
-    void close() {
-        try {
-            // Remote side of ParcelFileDescriptor is tracking the close of mDeviceFd, and unmount
-            // the corresponding fuse file system. The mMessageThread will receive ENODEV, and
-            // then terminate itself.
-            mDeviceFd.close();
-            mMessageThread.join();
-        } catch (IOException exp) {
-            Log.e(MtpDocumentsProvider.TAG, "Failed to close device FD.", exp);
-        } catch (InterruptedException exp) {
-            Log.e(MtpDocumentsProvider.TAG, "Failed to terminate message thread.", exp);
-        }
-    }
-
-    /**
-     * 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 |
-                         ParcelFileDescriptor.MODE_TRUNCATE));
-        return ParcelFileDescriptor.open(new File(
-                getMountPoint(),
-                Integer.toString(i)),
-                mode);
-    }
-
-    File getMountPoint() {
-        return new File("/mnt/appfuse/" + Process.myUid() + "_" + mName);
-    }
-
-    static interface Callback {
-        /**
-         * Returns file size for the given inode.
-         * @param inode
-         * @return File size. Must not be negative.
-         * @throws FileNotFoundException
-         */
-        long getFileSize(int inode) throws FileNotFoundException;
-
-        /**
-         * Returns file 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
-         */
-        long readObjectBytes(int inode, long offset, long size, byte[] bytes) throws IOException;
-
-        /**
-         * Handles writing bytes for the give inode.
-         * @param fileHandle
-         * @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(long fileHandle, int inode, long offset, int size, byte[] bytes)
-                throws IOException, ErrnoException;
-
-        /**
-         * Flushes bytes for file handle.
-         * @param fileHandle
-         * @throws IOException
-         * @throws ErrnoException
-         */
-        void flushFileHandle(long fileHandle) throws IOException, ErrnoException;
-
-        /**
-         * Closes file handle.
-         * @param fileHandle
-         * @throws IOException
-         */
-        void closeFileHandle(long fileHandle) throws IOException, ErrnoException;
-    }
-
-    @UsedByNative("com_android_mtp_AppFuse.cpp")
-    @WorkerThread
-    private long getFileSize(int inode) {
-        try {
-            return mCallback.getFileSize(inode);
-        } catch (Exception error) {
-            return -getErrnoFromException(error);
-        }
-    }
-
-    @UsedByNative("com_android_mtp_AppFuse.cpp")
-    @WorkerThread
-    private long readObjectBytes(int inode, long offset, long size) {
-        if (offset < 0 || size < 0 || size > MAX_READ) {
-            return -OsConstants.EINVAL;
-        }
-        try {
-            // It's OK to share the same mBuffer among requests because the requests are processed
-            // by AppFuseMessageThread sequentially.
-            return mCallback.readObjectBytes(inode, offset, size, mBuffer);
-        } catch (Exception error) {
-            return -getErrnoFromException(error);
-        }
-    }
-
-    @UsedByNative("com_android_mtp_AppFuse.cpp")
-    @WorkerThread
-    private /* unsgined */ int writeObjectBytes(long fileHandler,
-                                                int inode,
-                                                /* unsigned */ long offset,
-                                                /* unsigned */ int size,
-                                                byte[] bytes) {
-        try {
-            return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes);
-        } catch (Exception error) {
-            return -getErrnoFromException(error);
-        }
-    }
-
-    @UsedByNative("com_android_mtp_AppFuse.cpp")
-    @WorkerThread
-    private int flushFileHandle(long fileHandle) {
-        try {
-            mCallback.flushFileHandle(fileHandle);
-            return 0;
-        } catch (Exception error) {
-            return -getErrnoFromException(error);
-        }
-    }
-
-    @UsedByNative("com_android_mtp_AppFuse.cpp")
-    @WorkerThread
-    private int closeFileHandle(long fileHandle) {
-        try {
-            mCallback.closeFileHandle(fileHandle);
-            return 0;
-        } catch (Exception error) {
-            return -getErrnoFromException(error);
-        }
-    }
-
-    private static int getErrnoFromException(Exception error) {
-        if (DEBUG) {
-            Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error);
-        }
-        if (error instanceof FileNotFoundException) {
-            return OsConstants.ENOENT;
-        } else if (error instanceof IOException) {
-            return OsConstants.EIO;
-        } else if (error instanceof UnsupportedOperationException) {
-            return OsConstants.ENOTSUP;
-        } else if (error instanceof IllegalArgumentException) {
-            return OsConstants.EINVAL;
-        } else {
-            return OsConstants.EIO;
-        }
-    }
-
-    private native void native_start_app_fuse_loop(int fd);
-
-    private class AppFuseMessageThread extends Thread {
-        /**
-         * File descriptor used by native loop.
-         * It's owned by native loop and does not need to close here.
-         */
-        private final int mRawFd;
-
-        AppFuseMessageThread(int fd) {
-            super("AppFuseMessageThread");
-            mRawFd = fd;
-        }
-
-        @Override
-        public void run() {
-            native_start_app_fuse_loop(mRawFd);
-        }
-    }
-}
diff --git a/src/com/android/mtp/MtpDocumentsProvider.java b/src/com/android/mtp/MtpDocumentsProvider.java
index 6b2c1ee..8b0e610 100644
--- a/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/src/com/android/mtp/MtpDocumentsProvider.java
@@ -33,6 +33,7 @@
 import android.os.CancellationSignal;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Path;
@@ -41,6 +42,7 @@
 import android.provider.DocumentsProvider;
 import android.provider.Settings;
 import android.system.ErrnoException;
+import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -54,6 +56,8 @@
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
 
+import libcore.io.IoUtils;
+
 /**
  * DocumentsProvider for MTP devices.
  */
@@ -84,9 +88,9 @@
     private RootScanner mRootScanner;
     private Resources mResources;
     private MtpDatabase mDatabase;
-    private AppFuse mAppFuse;
     private ServiceIntentSender mIntentSender;
     private Context mContext;
+    private StorageManager mStorageManager;
 
     /**
      * Provides singleton instance to MtpDocumentsService.
@@ -105,8 +109,8 @@
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
         mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
-        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
         mIntentSender = new ServiceIntentSender(getContext());
+        mStorageManager = getContext().getSystemService(StorageManager.class);
 
         // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider
         // after booting.
@@ -129,14 +133,6 @@
             return false;
         }
 
-        // TODO: Mount AppFuse on demands.
-        try {
-            mAppFuse.mount(getContext().getSystemService(StorageManager.class));
-        } catch (IOException error) {
-            Log.e(TAG, "Failed to start app fuse.", error);
-            return false;
-        }
-
         resume();
         return true;
     }
@@ -157,16 +153,9 @@
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
         mDatabase = database;
         mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
-        mAppFuse = new AppFuse(TAG, new AppFuseCallback());
         mIntentSender = intentSender;
+        mStorageManager = storageManager;
 
-        // TODO: Mount AppFuse on demands.
-        try {
-            mAppFuse.mount(storageManager);
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to start app fuse.", e);
-            return false;
-        }
         resume();
         return true;
     }
@@ -252,7 +241,10 @@
                 }
                 if (MtpDeviceRecord.isPartialReadSupported(
                         device.operationsSupported, fileSize)) {
-                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+
+                    return mStorageManager.openProxyFileDescriptor(
+                            modeFlag,
+                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
                 } else {
                     // If getPartialObject{|64} are not supported for the device, returns
                     // non-seekable pipe FD instead.
@@ -262,7 +254,9 @@
                 // TODO: Clear the parent document loader task (if exists) and call notify
                 // when writing is completed.
                 if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) {
-                    return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag);
+                    return mStorageManager.openProxyFileDescriptor(
+                            modeFlag,
+                            new MtpProxyFileDescriptorCallback(Integer.parseInt(documentId)));
                 } else {
                     throw new UnsupportedOperationException(
                             "The device does not support writing operation.");
@@ -586,7 +580,6 @@
                 throw new RuntimeException(e);
             } finally {
                 mDatabase.close();
-                mAppFuse.close();
                 super.shutdown();
             }
         }
@@ -693,72 +686,92 @@
         }
     }
 
-    private class AppFuseCallback implements AppFuse.Callback {
-        private final Map<Long, MtpFileWriter> mWriters = new HashMap<>();
+    private class MtpProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
+        private final int mInode;
+        private MtpFileWriter mWriter;
 
-        @Override
-        public long getFileSize(int inode) throws FileNotFoundException {
-            return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode));
+        MtpProxyFileDescriptorCallback(int inode) {
+            mInode = inode;
         }
 
         @Override
-        public long readObjectBytes(
-                int inode, long offset, long size, byte[] buffer) throws IOException {
-            final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode));
-            final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
-
-            if (MtpDeviceRecord.isSupported(
-                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
-                return mMtpManager.getPartialObject64(
-                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
-            }
-
-            if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
-                    record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
-                return mMtpManager.getPartialObject(
-                        identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer);
-            }
-
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int writeObjectBytes(
-                long fileHandle, int inode, long offset, int size, byte[] bytes)
-                throws IOException, ErrnoException {
-            final MtpFileWriter writer;
-            if (mWriters.containsKey(fileHandle)) {
-                writer = mWriters.get(fileHandle);
-            } else {
-                writer = new MtpFileWriter(mContext, String.valueOf(inode));
-                mWriters.put(fileHandle, writer);
-            }
-            return writer.write(offset, size, bytes);
-        }
-
-        @Override
-        public void flushFileHandle(long fileHandle) throws IOException, ErrnoException {
-            final MtpFileWriter writer = mWriters.get(fileHandle);
-            if (writer == null) {
-                // File handle for reading.
-                return;
-            }
-            final MtpDeviceRecord device = getDeviceToolkit(
-                    mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord;
-            writer.flush(mMtpManager, mDatabase, device.operationsSupported);
-        }
-
-        @Override
-        public void closeFileHandle(long fileHandle) throws IOException, ErrnoException {
-            final MtpFileWriter writer = mWriters.get(fileHandle);
-            if (writer == null) {
-                // File handle for reading.
-                return;
-            }
+        public long onGetSize() throws ErrnoException {
             try {
-                writer.close();
+                return getFileSize(String.valueOf(mInode));
+            } catch (FileNotFoundException e) {
+                Log.e(TAG, e.getMessage(), e);
+                throw new ErrnoException("onGetSize", OsConstants.ENOENT);
+            }
+        }
+
+        @Override
+        public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+            try {
+                final Identifier identifier = mDatabase.createIdentifier(Integer.toString(mInode));
+                final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord;
+                if (MtpDeviceRecord.isSupported(
+                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT_64)) {
+
+                        return (int) mMtpManager.getPartialObject64(
+                                identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
+
+                }
+                if (0 <= offset && offset <= 0xffffffffL && MtpDeviceRecord.isSupported(
+                        record.operationsSupported, MtpConstants.OPERATION_GET_PARTIAL_OBJECT)) {
+                    return (int) mMtpManager.getPartialObject(
+                            identifier.mDeviceId, identifier.mObjectHandle, offset, size, data);
+                }
+                throw new ErrnoException("onRead", OsConstants.ENOTSUP);
+            } catch (IOException e) {
+                Log.e(TAG, e.getMessage(), e);
+                throw new ErrnoException("onRead", OsConstants.EIO);
+            }
+        }
+
+        @Override
+        public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+            try {
+                if (mWriter == null) {
+                    mWriter = new MtpFileWriter(mContext, String.valueOf(mInode));
+                }
+                return mWriter.write(offset, size, data);
+            } catch (IOException e) {
+                Log.e(TAG, e.getMessage(), e);
+                throw new ErrnoException("onWrite", OsConstants.EIO);
+            }
+        }
+
+        @Override
+        public void onFsync() throws ErrnoException {
+            tryFsync();
+        }
+
+        @Override
+        public void onRelease() {
+            try {
+                tryFsync();
+            } catch (ErrnoException error) {
+                // Cannot recover from the error at onRelease. Client app should use fsync to
+                // ensure the provider writes data correctly.
+                Log.e(TAG, "Cannot recover from the error at onRelease.", error);
             } finally {
-                mWriters.remove(fileHandle);
+                if (mWriter != null) {
+                    IoUtils.closeQuietly(mWriter);
+                }
+            }
+        }
+
+        private void tryFsync() throws ErrnoException {
+            try {
+                if (mWriter != null) {
+                    final MtpDeviceRecord device =
+                            getDeviceToolkit(mDatabase.createIdentifier(
+                                    mWriter.getDocumentId()).mDeviceId).mDeviceRecord;
+                    mWriter.flush(mMtpManager, mDatabase, device.operationsSupported);
+                }
+            } catch (IOException e) {
+                Log.e(TAG, e.getMessage(), e);
+                throw new ErrnoException("onWrite", OsConstants.EIO);
             }
         }
     }
diff --git a/tests/src/com/android/mtp/AppFuseTest.java b/tests/src/com/android/mtp/AppFuseTest.java
deleted file mode 100644
index e421de7..0000000
--- a/tests/src/com/android/mtp/AppFuseTest.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2015 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;
-
-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.MediumTest;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-
-@MediumTest
-public class AppFuseTest extends AndroidTestCase {
-    public void testMount() throws ErrnoException, IOException {
-        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
-        final AppFuse appFuse = new AppFuse("test", new TestCallback());
-        appFuse.mount(storageManager);
-        final File file = appFuse.getMountPoint();
-        assertTrue(file.isDirectory());
-        assertEquals(1, Os.stat(file.getPath()).st_ino);
-        appFuse.close();
-        assertTrue(1 != Os.stat(file.getPath()).st_ino);
-    }
-
-    public void 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, ParcelFileDescriptor.MODE_READ_ONLY);
-        fd.close();
-        appFuse.close();
-    }
-
-    public void testOpenFile_fileNotFound() 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_ONLY);
-            fail();
-        } 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();
-    }
-
-    public void testReadFile() throws IOException {
-        final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
-        final int fileInode = 10;
-        final byte[] fileBytes = 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 == fileInode) {
-                            return fileBytes.length;
-                        }
-                        return super.getFileSize(inode);
-                    }
-
-                    @Override
-                    public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
-                            throws IOException {
-                        if (inode == fileInode) {
-                            int i = 0;
-                            while (i < size && i + offset < fileBytes.length)  {
-                                bytes[i] = fileBytes[(int) (i + offset)];
-                                i++;
-                            }
-                            return i;
-                        }
-                        return super.readObjectBytes(inode, offset, size, bytes);
-                    }
-                });
-        appFuse.mount(storageManager);
-        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];
-            final int size = stream.read(buffer, 0, buffer.length);
-            assertEquals(5, size);
-        }
-        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(
-                            long fileHandle, 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 | ParcelFileDescriptor.MODE_TRUNCATE);
-        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 | ParcelFileDescriptor.MODE_TRUNCATE);
-        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
-                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
-            stream.write('a');
-            fail();
-        } catch (IOException e) {
-        }
-        appFuse.close();
-    }
-
-    public void testWriteFile_flushError() 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;
-                    }
-
-                    @Override
-                    public int writeObjectBytes(
-                            long fileHandle, int inode, long offset, int size, byte[] bytes) {
-                        return size;
-                    }
-
-                    @Override
-                    public void flushFileHandle(long fileHandle) throws IOException {
-                        throw new IOException();
-                    }
-                });
-        appFuse.mount(storageManager);
-        final ParcelFileDescriptor fd = appFuse.openFile(
-                INODE, ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE);
-        try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
-                new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
-            stream.write('a');
-            try {
-                IoUtils.close(fd.getFileDescriptor());
-                fail();
-            } catch (IOException e) {
-            }
-        }
-        appFuse.close();
-    }
-
-    private static class TestCallback implements AppFuse.Callback {
-        @Override
-        public long getFileSize(int inode) throws FileNotFoundException {
-            throw new FileNotFoundException();
-        }
-
-        @Override
-        public long readObjectBytes(int inode, long offset, long size, byte[] bytes)
-                throws IOException {
-            throw new IOException();
-        }
-
-        @Override
-        public int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes)
-                throws IOException {
-            throw new IOException();
-        }
-
-        @Override
-        public void flushFileHandle(long fileHandle) throws IOException {}
-
-        @Override
-        public void closeFileHandle(long fileHandle) {}
-    }
-}
diff --git a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index a9d35e1..491e24d 100644
--- a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -557,6 +557,7 @@
             try (ParcelFileDescriptor.AutoCloseOutputStream stream =
                     new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
                 stream.write("Hello".getBytes());
+                fd.getFileDescriptor().sync();
             }
         }
         {