From e80ea38489a7fca0e5a8cf2f663704058c782d63 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Tue, 15 Nov 2016 13:07:01 +0900 Subject: [PATCH] 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 --- packages/MtpDocumentsProvider/Android.mk | 1 - packages/MtpDocumentsProvider/jni/Android.mk | 15 - .../jni/com_android_mtp_AppFuse.cpp | 571 ------------------ .../src/com/android/mtp/AppFusePerfTest.java | 64 +- .../src/com/android/mtp/AppFuse.java | 258 -------- .../com/android/mtp/MtpDocumentsProvider.java | 175 +++--- .../src/com/android/mtp/AppFuseTest.java | 262 -------- .../android/mtp/MtpDocumentsProviderTest.java | 1 + 8 files changed, 112 insertions(+), 1235 deletions(-) delete mode 100644 packages/MtpDocumentsProvider/jni/Android.mk delete mode 100644 packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp delete mode 100644 packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java delete mode 100644 packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk index c9e7195808d22..a9e9b2e1a6211 100644 --- a/packages/MtpDocumentsProvider/Android.mk +++ b/packages/MtpDocumentsProvider/Android.mk @@ -6,7 +6,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) 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/packages/MtpDocumentsProvider/jni/Android.mk b/packages/MtpDocumentsProvider/jni/Android.mk deleted file mode 100644 index d545b14df4379..0000000000000 --- a/packages/MtpDocumentsProvider/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/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp b/packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp deleted file mode 100644 index e948cf7f2685b..0000000000000 --- a/packages/MtpDocumentsProvider/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 -#include -#include - -#include -#include -#include - -#include - -#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 -class FuseResponse { - size_t size_; - T* const buffer_; -public: - explicit FuseResponse(void* buffer) : size_(0), buffer_(static_cast(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 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* 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* 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* 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* 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* out) { - if (in->size > MAX_READ) { - return -EINVAL; - } - const std::map::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* out) { - if (in->size > MAX_WRITE) { - return -EINVAL; - } - const std::map::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(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* /* 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* /* out */) { - return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh)); - } - - template - void invoke_handler(int fd, - FuseRequest* request, - int (AppFuse::*handler)(const fuse_in_header&, - const T*, - FuseResponse*)) { - FuseResponse response(request->data()); - const int reply_code = (this->*handler)( - request->header(), - static_cast(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(env_->CallLongMethod( - self_, - app_fuse_get_file_size, - static_cast(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(inode), - static_cast(offset), - static_cast(size)); - if (read_size <= 0) { - return read_size; - } - ScopedLocalRef array( - env_, static_cast(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 array( - env_, - static_cast(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(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(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(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(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(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(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/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java b/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java index 0762571910dca..36f6fe97f48c0 100644 --- a/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java +++ b/packages/MtpDocumentsProvider/perf_tests/src/com/android/mtp/AppFusePerfTest.java @@ -20,6 +20,7 @@ import android.app.Activity; 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 @@ import org.junit.Test; @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 @@ public class AppFusePerfTest { 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 @@ public class AppFusePerfTest { } } - appFuse.close(); - double readAverage = 0; double writeAverage = 0; double readSquaredAverage = 0; @@ -127,28 +99,26 @@ public class AppFusePerfTest { 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/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java deleted file mode 100644 index cd78e6106540a..0000000000000 --- a/packages/MtpDocumentsProvider/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/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 6b2c1eeef694b..8b0e610c9af8c 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -33,6 +33,7 @@ import android.os.Bundle; 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.DocumentsContract; 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.List; import java.util.Map; import java.util.concurrent.TimeoutException; +import libcore.io.IoUtils; + /** * DocumentsProvider for MTP devices. */ @@ -84,9 +88,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDeviceToolkits = new HashMap(); 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDeviceToolkits = new HashMap(); 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { } 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { // 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 @@ public class MtpDocumentsProvider extends DocumentsProvider { throw new RuntimeException(e); } finally { mDatabase.close(); - mAppFuse.close(); super.shutdown(); } } @@ -693,72 +686,92 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } - private class AppFuseCallback implements AppFuse.Callback { - private final Map 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/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/AppFuseTest.java deleted file mode 100644 index e421de7113de7..0000000000000 --- a/packages/MtpDocumentsProvider/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/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index a9d35e1dedf89..491e24deefa2e 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -557,6 +557,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { try (ParcelFileDescriptor.AutoCloseOutputStream stream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { stream.write("Hello".getBytes()); + fd.getFileDescriptor().sync(); } } {