Merge "liboboe: fix typo in OboeStream_createThread() name"
diff --git a/include/media/IMediaExtractor.h b/include/media/IMediaExtractor.h
index 34b15e9..e0a81f1 100644
--- a/include/media/IMediaExtractor.h
+++ b/include/media/IMediaExtractor.h
@@ -54,8 +54,6 @@
     virtual uint32_t flags() const = 0;
 
     // for DRM
-    virtual void setDrmFlag(bool flag) = 0;
-    virtual bool getDrmFlag() = 0;
     virtual char* getDrmTrackInfo(size_t trackID, int *len)  = 0;
     virtual void setUID(uid_t uid)  = 0;
 
diff --git a/include/media/stagefright/MediaExtractor.h b/include/media/stagefright/MediaExtractor.h
index e5ee72e..4de1521 100644
--- a/include/media/stagefright/MediaExtractor.h
+++ b/include/media/stagefright/MediaExtractor.h
@@ -59,12 +59,6 @@
     virtual uint32_t flags() const;
 
     // for DRM
-    void setDrmFlag(bool flag) {
-        mIsDrm = flag;
-    };
-    bool getDrmFlag() {
-        return mIsDrm;
-    }
     virtual char* getDrmTrackInfo(size_t trackID, int *len) {
         return NULL;
     }
diff --git a/include/media/stagefright/SimpleDecodingSource.h b/include/media/stagefright/SimpleDecodingSource.h
index 534097b..e6aee6a 100644
--- a/include/media/stagefright/SimpleDecodingSource.h
+++ b/include/media/stagefright/SimpleDecodingSource.h
@@ -71,12 +71,13 @@
     // Construct this using a codec, source and looper.
     SimpleDecodingSource(
             const sp<MediaCodec> &codec, const sp<IMediaSource> &source, const sp<ALooper> &looper,
-            bool usingSurface, const sp<AMessage> &format);
+            bool usingSurface, bool isVorbis, const sp<AMessage> &format);
 
     sp<MediaCodec> mCodec;
     sp<IMediaSource> mSource;
     sp<ALooper> mLooper;
     bool mUsingSurface;
+    bool mIsVorbis;
     enum State {
         INIT,
         STARTED,
diff --git a/media/libmedia/IMediaExtractor.cpp b/media/libmedia/IMediaExtractor.cpp
index 94c96f6..7776313 100644
--- a/media/libmedia/IMediaExtractor.cpp
+++ b/media/libmedia/IMediaExtractor.cpp
@@ -34,8 +34,6 @@
     GETTRACKMETADATA,
     GETMETADATA,
     FLAGS,
-    SETDRMFLAG,
-    GETDRMFLAG,
     GETDRMTRACKINFO,
     SETUID,
     NAME
@@ -101,13 +99,6 @@
         return 0;
     }
 
-    virtual void setDrmFlag(bool flag __unused) {
-        ALOGV("setDrmFlag NOT IMPLEMENTED");
-    }
-    virtual bool getDrmFlag() {
-        ALOGV("getDrmFlag NOT IMPLEMENTED");
-       return false;
-    }
     virtual char* getDrmTrackInfo(size_t trackID __unused, int *len __unused) {
         ALOGV("getDrmTrackInfo NOT IMPLEMENTED");
         return NULL;
diff --git a/media/libmediaplayerservice/ActivityManager.cpp b/media/libmediaplayerservice/ActivityManager.cpp
index 0e6cf7b..438d422 100644
--- a/media/libmediaplayerservice/ActivityManager.cpp
+++ b/media/libmediaplayerservice/ActivityManager.cpp
@@ -14,18 +14,14 @@
  * limitations under the License.
  */
 
-#include <unistd.h>
+#include <binder/IActivityManager.h>
 #include <binder/IBinder.h>
 #include <binder/IServiceManager.h>
-#include <binder/Parcel.h>
-#include <utils/String8.h>
 
 #include "ActivityManager.h"
 
 namespace android {
 
-const uint32_t OPEN_CONTENT_URI_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION;
-
 // Perform ContentProvider.openFile() on the given URI, returning
 // the resulting native file descriptor.  Returns < 0 on error.
 int openContentProviderFile(const String16& uri)
@@ -33,26 +29,10 @@
     int fd = -1;
 
     sp<IServiceManager> sm = defaultServiceManager();
-    sp<IBinder> am = sm->getService(String16("activity"));
+    sp<IBinder> binder = sm->getService(String16("activity"));
+    sp<IActivityManager> am = interface_cast<IActivityManager>(binder);
     if (am != NULL) {
-        Parcel data, reply;
-        data.writeInterfaceToken(String16("android.app.IActivityManager"));
-        data.writeString16(uri);
-        status_t ret = am->transact(OPEN_CONTENT_URI_TRANSACTION, data, &reply);
-        if (ret == NO_ERROR) {
-            int32_t exceptionCode = reply.readExceptionCode();
-            if (!exceptionCode) {
-                // Success is indicated here by a nonzero int followed by the fd;
-                // failure by a zero int with no data following.
-                if (reply.readInt32() != 0) {
-                    fd = dup(reply.readFileDescriptor());
-                }
-            } else {
-                // An exception was thrown back; fall through to return failure
-                ALOGD("openContentUri(%s) caught exception %d\n",
-                        String8(uri).string(), exceptionCode);
-            }
-        }
+        fd = am->openContentUri(uri);
     }
 
     return fd;
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index 8761e9d..d1d1077 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -145,10 +145,6 @@
         return UNKNOWN_ERROR;
     }
 
-    if (extractor->getDrmFlag()) {
-        checkDrmStatus(mDataSource);
-    }
-
     mFileMeta = extractor->getMetaData();
     if (mFileMeta != NULL) {
         int64_t duration;
@@ -262,18 +258,6 @@
     return OK;
 }
 
-void NuPlayer::GenericSource::checkDrmStatus(const sp<DataSource>& dataSource) {
-    dataSource->getDrmInfo(mDecryptHandle, &mDrmManagerClient);
-    if (mDecryptHandle != NULL) {
-        CHECK(mDrmManagerClient);
-        if (RightsStatus::RIGHTS_VALID != mDecryptHandle->status) {
-            sp<AMessage> msg = dupNotify();
-            msg->setInt32("what", kWhatDrmNoLicense);
-            msg->post();
-        }
-    }
-}
-
 int64_t NuPlayer::GenericSource::getLastReadPosition() {
     if (mAudioTrack.mSource != NULL) {
         return mAudioTimeUs;
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h
index 38d8616..a14056f 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.h
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.h
@@ -234,7 +234,6 @@
     void resetDataSource();
 
     status_t initFromDataSource();
-    void checkDrmStatus(const sp<DataSource>& dataSource);
     int64_t getLastReadPosition();
     void setDrmPlaybackStatusIfNeeded(int playbackStatus, int64_t position);
 
diff --git a/media/libstagefright/DRMExtractor.cpp b/media/libstagefright/DRMExtractor.cpp
index d36ac65..8ba36d5 100644
--- a/media/libstagefright/DRMExtractor.cpp
+++ b/media/libstagefright/DRMExtractor.cpp
@@ -244,7 +244,6 @@
       mDecryptHandle(NULL),
       mDrmManagerClient(NULL) {
     mOriginalExtractor = MediaExtractor::Create(source, mime);
-    mOriginalExtractor->setDrmFlag(true);
     mOriginalExtractor->getMetaData()->setInt32(kKeyIsDRM, 1);
 
     source->getDrmInfo(mDecryptHandle, &mDrmManagerClient);
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
index aeaead5..49f480d 100644
--- a/media/libstagefright/MediaExtractor.cpp
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -160,6 +160,8 @@
             if (!strncmp(drmMime, "drm+es_based+", 13)) {
                 // DRMExtractor sets container metadata kKeyIsDRM to 1
                 return new DRMExtractor(source, drmMime + 14);
+            } else {
+                mime = drmMime + 20; // get real mimetype after "drm+container_based+" prefix
             }
         }
 
@@ -201,28 +203,6 @@
              mime, confidence);
     }
 
-    bool isDrm = false;
-    // DRM MIME type syntax is "drm+type+original" where
-    // type is "es_based" or "container_based" and
-    // original is the content's cleartext MIME type
-    if (!strncmp(mime, "drm+", 4)) {
-        const char *originalMime = strchr(mime+4, '+');
-        if (originalMime == NULL) {
-            // second + not found
-            return NULL;
-        }
-        ++originalMime;
-        if (!strncmp(mime, "drm+es_based+", 13)) {
-            // DRMExtractor sets container metadata kKeyIsDRM to 1
-            return new DRMExtractor(source, originalMime);
-        } else if (!strncmp(mime, "drm+container_based+", 20)) {
-            mime = originalMime;
-            isDrm = true;
-        } else {
-            return NULL;
-        }
-    }
-
     MediaExtractor *ret = NULL;
     if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
             || !strcasecmp(mime, "audio/mp4")) {
@@ -250,14 +230,6 @@
         ret = new MidiExtractor(source);
     }
 
-    if (ret != NULL) {
-       if (isDrm) {
-           ret->setDrmFlag(true);
-       } else {
-           ret->setDrmFlag(false);
-       }
-    }
-
     return ret;
 }
 
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 6f8220f..d25ce6c 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -82,14 +82,6 @@
         return ERROR_UNSUPPORTED;
     }
 
-    sp<MetaData> fileMeta = mImpl->getMetaData();
-    if (mImpl->getDrmFlag()) {
-        // Don't expose decrypted content to Java application
-        mImpl.clear();
-        mImpl = NULL;
-        return ERROR_UNSUPPORTED;
-    }
-
     status_t err = updateDurationAndBitrate();
     if (err == OK) {
         mDataSource = dataSource;
diff --git a/media/libstagefright/SimpleDecodingSource.cpp b/media/libstagefright/SimpleDecodingSource.cpp
index 4c4d93e..ea7d5af 100644
--- a/media/libstagefright/SimpleDecodingSource.cpp
+++ b/media/libstagefright/SimpleDecodingSource.cpp
@@ -18,6 +18,7 @@
 
 #include <media/ICrypto.h>
 #include <media/MediaCodecBuffer.h>
+#include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/foundation/ALooper.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/AUtils.h>
@@ -74,7 +75,10 @@
                 err = codec->getOutputFormat(&format);
             }
             if (err == OK) {
-                return new SimpleDecodingSource(codec, source, looper, surface != NULL, format);
+                return new SimpleDecodingSource(codec, source, looper,
+                        surface != NULL,
+                        strcmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS) == 0,
+                        format);
             }
 
             ALOGD("Failed to configure codec '%s'", componentName.c_str());
@@ -90,11 +94,12 @@
 
 SimpleDecodingSource::SimpleDecodingSource(
         const sp<MediaCodec> &codec, const sp<IMediaSource> &source, const sp<ALooper> &looper,
-        bool usingSurface, const sp<AMessage> &format)
+        bool usingSurface, bool isVorbis, const sp<AMessage> &format)
     : mCodec(codec),
       mSource(source),
       mLooper(looper),
       mUsingSurface(usingSurface),
+      mIsVorbis(isVorbis),
       mProtectedState(format) {
     mCodec->getName(&mComponentName);
 }
@@ -280,16 +285,25 @@
             if (in_buf != NULL) {
                 int64_t timestampUs = 0;
                 CHECK(in_buf->meta_data()->findInt64(kKeyTime, &timestampUs));
-                if (in_buf->range_length() > in_buffer->capacity()) {
+                if (in_buf->range_length() + (mIsVorbis ? 4 : 0) > in_buffer->capacity()) {
                     ALOGW("'%s' received %zu input bytes for buffer of size %zu",
                             mComponentName.c_str(),
-                            in_buf->range_length(), in_buffer->capacity());
+                            in_buf->range_length() + (mIsVorbis ? 4 : 0), in_buffer->capacity());
                 }
+                size_t cpLen = min(in_buf->range_length(), in_buffer->capacity());
                 memcpy(in_buffer->base(), (uint8_t *)in_buf->data() + in_buf->range_offset(),
-                       min(in_buf->range_length(), in_buffer->capacity()));
+                        cpLen );
+
+                if (mIsVorbis) {
+                    int32_t numPageSamples;
+                    if (!in_buf->meta_data()->findInt32(kKeyValidSamples, &numPageSamples)) {
+                        numPageSamples = -1;
+                    }
+                    memcpy(in_buffer->base() + cpLen, &numPageSamples, sizeof(numPageSamples));
+                }
 
                 res = mCodec->queueInputBuffer(
-                        in_ix, 0 /* offset */, in_buf->range_length(),
+                        in_ix, 0 /* offset */, in_buf->range_length() + (mIsVorbis ? 4 : 0),
                         timestampUs, 0 /* flags */);
                 if (res != OK) {
                     ALOGI("[%s] failed to queue input buffer #%zu", mComponentName.c_str(), in_ix);
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 5e00c44..883a4dd 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -783,11 +783,6 @@
             }
         }
     }
-
-    // To check whether the media file is drm-protected
-    if (mExtractor->getDrmFlag()) {
-        mMetaData.add(METADATA_KEY_IS_DRM, String8("1"));
-    }
 }
 
 void StagefrightMetadataRetriever::clearMetadata() {
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk
index cb7e4aa..58753ff 100644
--- a/media/mtp/Android.mk
+++ b/media/mtp/Android.mk
@@ -19,26 +19,31 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=                                       \
+                  AsyncIO.cpp                           \
                   MtpDataPacket.cpp                     \
                   MtpDebug.cpp                          \
+                  MtpDevHandle.cpp                      \
                   MtpDevice.cpp                         \
-                  MtpEventPacket.cpp                    \
                   MtpDeviceInfo.cpp                     \
+                  MtpEventPacket.cpp                    \
+                  MtpFfsHandle.cpp                      \
                   MtpObjectInfo.cpp                     \
                   MtpPacket.cpp                         \
                   MtpProperty.cpp                       \
                   MtpRequestPacket.cpp                  \
                   MtpResponsePacket.cpp                 \
                   MtpServer.cpp                         \
+                  MtpStorage.cpp                        \
                   MtpStorageInfo.cpp                    \
                   MtpStringBuffer.cpp                   \
-                  MtpStorage.cpp                        \
                   MtpUtils.cpp                          \
 
 LOCAL_MODULE:= libmtp
 
 LOCAL_CFLAGS := -DMTP_DEVICE -DMTP_HOST -Wall -Wextra -Werror
 
-LOCAL_SHARED_LIBRARIES := libutils libcutils liblog libusbhost libbinder
+LOCAL_SHARED_LIBRARIES := libbase libutils libcutils liblog libusbhost libbinder
 
 include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/mtp/AsyncIO.cpp b/media/mtp/AsyncIO.cpp
new file mode 100644
index 0000000..a1a98ab
--- /dev/null
+++ b/media/mtp/AsyncIO.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+#include "AsyncIO.h"
+
+namespace {
+
+void read_func(struct aiocb *aiocbp) {
+    aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes,
+                aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+    if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void write_func(struct aiocb *aiocbp) {
+    aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+                aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset));
+    if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void splice_read_func(struct aiocb *aiocbp) {
+    aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes,
+                (off64_t*) &aiocbp->aio_offset, aiocbp->aio_sink,
+                NULL, aiocbp->aio_nbytes, 0));
+    if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+void splice_write_func(struct aiocb *aiocbp) {
+    aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes, NULL,
+                aiocbp->aio_sink, (off64_t*) &aiocbp->aio_offset,
+                aiocbp->aio_nbytes, 0));
+    if (aiocbp->ret == -1) aiocbp->error = errno;
+}
+
+std::queue<std::unique_ptr<struct aiocb>> queue;
+std::mutex queue_lock;
+std::condition_variable queue_cond;
+std::condition_variable write_cond;
+int done = 1;
+void splice_write_pool_func(int) {
+    while(1) {
+        std::unique_lock<std::mutex> lk(queue_lock);
+        queue_cond.wait(lk, []{return !queue.empty() || done;});
+        if (queue.empty() && done) {
+            return;
+        }
+        std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
+        queue.pop();
+        lk.unlock();
+        write_cond.notify_one();
+        splice_write_func(aiocbp.get());
+        close(aiocbp->aio_fildes);
+    }
+}
+
+void write_pool_func(int) {
+    while(1) {
+        std::unique_lock<std::mutex> lk(queue_lock);
+        queue_cond.wait(lk, []{return !queue.empty() || done;});
+        if (queue.empty() && done) {
+            return;
+        }
+        std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front());
+        queue.pop();
+        lk.unlock();
+        write_cond.notify_one();
+        aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes,
+                    aiocbp->aio_pool_buf.get(), aiocbp->aio_nbytes, aiocbp->aio_offset));
+        if (aiocbp->ret == -1) aiocbp->error = errno;
+    }
+}
+
+constexpr int NUM_THREADS = 1;
+constexpr int MAX_QUEUE_SIZE = 10;
+std::thread pool[NUM_THREADS];
+
+} // end anonymous namespace
+
+void aio_pool_init(void(f)(int)) {
+    CHECK(done == 1);
+    done = 0;
+    for (int i = 0; i < NUM_THREADS; i++) {
+        pool[i] = std::thread(f, i);
+    }
+}
+
+void aio_pool_splice_init() {
+    aio_pool_init(splice_write_pool_func);
+}
+
+void aio_pool_write_init() {
+    aio_pool_init(write_pool_func);
+}
+
+void aio_pool_end() {
+    done = 1;
+    for (int i = 0; i < NUM_THREADS; i++) {
+        std::unique_lock<std::mutex> lk(queue_lock);
+        lk.unlock();
+        queue_cond.notify_one();
+    }
+
+    for (int i = 0; i < NUM_THREADS; i++) {
+        pool[i].join();
+    }
+}
+
+// used for both writes and splices depending on which init was used before.
+int aio_pool_write(struct aiocb *aiocbp) {
+    std::unique_lock<std::mutex> lk(queue_lock);
+    write_cond.wait(lk, []{return queue.size() < MAX_QUEUE_SIZE;});
+    queue.push(std::unique_ptr<struct aiocb>(aiocbp));
+    lk.unlock();
+    queue_cond.notify_one();
+    return 0;
+}
+
+int aio_read(struct aiocb *aiocbp) {
+    aiocbp->thread = std::thread(read_func, aiocbp);
+    return 0;
+}
+
+int aio_write(struct aiocb *aiocbp) {
+    aiocbp->thread = std::thread(write_func, aiocbp);
+    return 0;
+}
+
+int aio_splice_read(struct aiocb *aiocbp) {
+    aiocbp->thread = std::thread(splice_read_func, aiocbp);
+    return 0;
+}
+
+int aio_splice_write(struct aiocb *aiocbp) {
+    aiocbp->thread = std::thread(splice_write_func, aiocbp);
+    return 0;
+}
+
+int aio_error(const struct aiocb *aiocbp) {
+    return aiocbp->error;
+}
+
+ssize_t aio_return(struct aiocb *aiocbp) {
+    return aiocbp->ret;
+}
+
+int aio_suspend(struct aiocb *aiocbp[], int n,
+        const struct timespec *) {
+    for (int i = 0; i < n; i++) {
+        aiocbp[i]->thread.join();
+    }
+    return 0;
+}
+
+int aio_cancel(int, struct aiocb *) {
+    // Not implemented
+    return -1;
+}
+
diff --git a/media/mtp/AsyncIO.h b/media/mtp/AsyncIO.h
new file mode 100644
index 0000000..f7515a2
--- /dev/null
+++ b/media/mtp/AsyncIO.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ASYNCIO_H
+#define _ASYNCIO_H
+
+#include <fcntl.h>
+#include <linux/aio_abi.h>
+#include <memory>
+#include <signal.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <time.h>
+#include <thread>
+#include <unistd.h>
+
+/**
+ * Provides a subset of POSIX aio operations, as well
+ * as similar operations with splice and threadpools.
+ */
+
+struct aiocb {
+    int aio_fildes;     // Assumed to be the source for splices
+    void *aio_buf;      // Unused for splices
+
+    // Used for threadpool operations only, freed automatically
+    std::unique_ptr<char[]> aio_pool_buf;
+
+    off_t aio_offset;
+    size_t aio_nbytes;
+
+    int aio_sink;       // Unused for non splice r/w
+
+    // Used internally
+    std::thread thread;
+    ssize_t ret;
+    int error;
+};
+
+// Submit a request for IO to be completed
+int aio_read(struct aiocb *);
+int aio_write(struct aiocb *);
+int aio_splice_read(struct aiocb *);
+int aio_splice_write(struct aiocb *);
+
+// Suspend current thread until given IO is complete, at which point
+// its return value and any errors can be accessed
+int aio_suspend(struct aiocb *[], int, const struct timespec *);
+int aio_error(const struct aiocb *);
+ssize_t aio_return(struct aiocb *);
+int aio_cancel(int, struct aiocb *);
+
+// Initialize a threadpool to perform IO. Only one pool can be
+// running at a time.
+void aio_pool_write_init();
+void aio_pool_splice_init();
+// Suspend current thread until all queued work is complete, then ends the threadpool
+void aio_pool_end();
+// Submit IO work for the threadpool to complete. Memory associated with the work is
+// freed automatically when the work is complete.
+int aio_pool_write(struct aiocb *);
+
+#endif // ASYNCIO_H
+
diff --git a/media/mtp/IMtpHandle.h b/media/mtp/IMtpHandle.h
new file mode 100644
index 0000000..9185255
--- /dev/null
+++ b/media/mtp/IMtpHandle.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _IMTP_HANDLE_H
+#define _IMTP_HANDLE_H
+
+#include <linux/usb/f_mtp.h>
+
+constexpr char FFS_MTP_EP0[] = "/dev/usb-ffs/mtp/ep0";
+
+class IMtpHandle {
+public:
+    // Return number of bytes read/written, or -1 and errno is set
+    virtual int read(void *data, int len) = 0;
+    virtual int write(const void *data, int len) = 0;
+
+    // Return 0 if send/receive is successful, or -1 and errno is set
+    virtual int receiveFile(mtp_file_range mfr) = 0;
+    virtual int sendFile(mtp_file_range mfr) = 0;
+    virtual int sendEvent(mtp_event me) = 0;
+
+    // Return 0 if operation is successful, or -1 else
+    virtual int start() = 0;
+    virtual int configure(bool ptp) = 0;
+
+    virtual void close() = 0;
+
+    virtual ~IMtpHandle() {}
+};
+
+IMtpHandle *get_ffs_handle();
+IMtpHandle *get_mtp_handle();
+
+#endif // _IMTP_HANDLE_H
+
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 0356753..d1c71d7 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 #include <usbhost/usbhost.h>
 #include "MtpStringBuffer.h"
+#include "IMtpHandle.h"
 
 namespace android {
 
@@ -438,9 +439,9 @@
         putUInt16(0);
 }
 
-#ifdef MTP_DEVICE 
-int MtpDataPacket::read(int fd) {
-    int ret = ::read(fd, mBuffer, MTP_BUFFER_SIZE);
+#ifdef MTP_DEVICE
+int MtpDataPacket::read(IMtpHandle *h) {
+    int ret = h->read(mBuffer, MTP_BUFFER_SIZE);
     if (ret < MTP_CONTAINER_HEADER_SIZE)
         return -1;
     mPacketSize = ret;
@@ -448,20 +449,20 @@
     return ret;
 }
 
-int MtpDataPacket::write(int fd) {
+int MtpDataPacket::write(IMtpHandle *h) {
     MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
     MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
-    int ret = ::write(fd, mBuffer, mPacketSize);
+    int ret = h->write(mBuffer, mPacketSize);
     return (ret < 0 ? ret : 0);
 }
 
-int MtpDataPacket::writeData(int fd, void* data, uint32_t length) {
+int MtpDataPacket::writeData(IMtpHandle *h, void* data, uint32_t length) {
     allocate(length + MTP_CONTAINER_HEADER_SIZE);
     memcpy(mBuffer + MTP_CONTAINER_HEADER_SIZE, data, length);
     length += MTP_CONTAINER_HEADER_SIZE;
     MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length);
     MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
-    int ret = ::write(fd, mBuffer, length);
+    int ret = h->write(mBuffer, length);
     return (ret < 0 ? ret : 0);
 }
 
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index 82e0ee4..a449d6f 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -20,6 +20,7 @@
 #include "MtpPacket.h"
 #include "mtp.h"
 
+class IMtpHandle;
 struct usb_device;
 struct usb_request;
 
@@ -94,12 +95,12 @@
     inline void         putEmptyArray() { putUInt32(0); }
 
 #ifdef MTP_DEVICE
-    // fill our buffer with data from the given file descriptor
-    int                 read(int fd);
+    // fill our buffer with data from the given usb handle
+    int                 read(IMtpHandle *h);
 
-    // write our data to the given file descriptor
-    int                 write(int fd);
-    int                 writeData(int fd, void* data, uint32_t length);
+    // write our data to the given usb handle
+    int                 write(IMtpHandle *h);
+    int                 writeData(IMtpHandle *h, void* data, uint32_t length);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpDevHandle.cpp b/media/mtp/MtpDevHandle.cpp
new file mode 100644
index 0000000..afc0525
--- /dev/null
+++ b/media/mtp/MtpDevHandle.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or         implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/Log.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <cutils/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <linux/usb/ch9.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include "IMtpHandle.h"
+
+constexpr char mtp_dev_path[] = "/dev/mtp_usb";
+
+class MtpDevHandle : public IMtpHandle {
+private:
+    android::base::unique_fd mFd;
+
+public:
+    MtpDevHandle();
+    ~MtpDevHandle();
+    int read(void *data, int len);
+    int write(const void *data, int len);
+
+    int receiveFile(mtp_file_range mfr);
+    int sendFile(mtp_file_range mfr);
+    int sendEvent(mtp_event me);
+
+    int start();
+    void close();
+
+    int configure(bool ptp);
+};
+
+MtpDevHandle::MtpDevHandle()
+    : mFd(-1) {};
+
+MtpDevHandle::~MtpDevHandle() {}
+
+int MtpDevHandle::read(void *data, int len) {
+    return ::read(mFd, data, len);
+}
+
+int MtpDevHandle::write(const void *data, int len) {
+    return ::write(mFd, data, len);
+}
+
+int MtpDevHandle::receiveFile(mtp_file_range mfr) {
+    return ioctl(mFd, MTP_RECEIVE_FILE, reinterpret_cast<unsigned long>(&mfr));
+}
+
+int MtpDevHandle::sendFile(mtp_file_range mfr) {
+    return ioctl(mFd, MTP_SEND_FILE_WITH_HEADER, reinterpret_cast<unsigned long>(&mfr));
+}
+
+int MtpDevHandle::sendEvent(mtp_event me) {
+    return ioctl(mFd, MTP_SEND_EVENT, reinterpret_cast<unsigned long>(&me));
+}
+
+int MtpDevHandle::start() {
+    mFd = android::base::unique_fd(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR)));
+    if (mFd == -1) return -1;
+    return 0;
+}
+
+void MtpDevHandle::close() {
+    mFd.reset();
+}
+
+int MtpDevHandle::configure(bool) {
+    // Nothing to do, driver can configure itself
+    return 0;
+}
+
+IMtpHandle *get_mtp_handle() {
+    return new MtpDevHandle();
+}
diff --git a/media/mtp/MtpEventPacket.cpp b/media/mtp/MtpEventPacket.cpp
index d9ef311..921ecbd 100644
--- a/media/mtp/MtpEventPacket.cpp
+++ b/media/mtp/MtpEventPacket.cpp
@@ -19,12 +19,8 @@
 #include <stdio.h>
 #include <sys/types.h>
 #include <fcntl.h>
-#include <sys/ioctl.h>
 
-#ifdef MTP_DEVICE
-#include <linux/usb/f_mtp.h>
-#endif
-
+#include "IMtpHandle.h"
 #include "MtpEventPacket.h"
 
 #include <usbhost/usbhost.h>
@@ -40,7 +36,7 @@
 }
 
 #ifdef MTP_DEVICE
-int MtpEventPacket::write(int fd) {
+int MtpEventPacket::write(IMtpHandle *h) {
     struct mtp_event    event;
 
     putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
@@ -48,7 +44,7 @@
 
     event.data = mBuffer;
     event.length = mPacketSize;
-    int ret = ::ioctl(fd, MTP_SEND_EVENT, (unsigned long)&event);
+    int ret = h->sendEvent(event);
     return (ret < 0 ? ret : 0);
 }
 #endif
diff --git a/media/mtp/MtpEventPacket.h b/media/mtp/MtpEventPacket.h
index a8779fd..3f3b6a3 100644
--- a/media/mtp/MtpEventPacket.h
+++ b/media/mtp/MtpEventPacket.h
@@ -20,6 +20,8 @@
 #include "MtpPacket.h"
 #include "mtp.h"
 
+class IMtpHandle;
+
 namespace android {
 
 class MtpEventPacket : public MtpPacket {
@@ -29,8 +31,8 @@
     virtual             ~MtpEventPacket();
 
 #ifdef MTP_DEVICE
-    // write our data to the given file descriptor
-    int                 write(int fd);
+    // write our data to the given usb handle
+    int                 write(IMtpHandle *h);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpFfsHandle.cpp b/media/mtp/MtpFfsHandle.cpp
new file mode 100644
index 0000000..10314e9
--- /dev/null
+++ b/media/mtp/MtpFfsHandle.cpp
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
+#include <mutex>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/endian.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <vector>
+
+#include "AsyncIO.h"
+#include "MtpFfsHandle.h"
+
+#define cpu_to_le16(x)  htole16(x)
+#define cpu_to_le32(x)  htole32(x)
+
+namespace {
+
+constexpr char FFS_MTP_EP_IN[] = "/dev/usb-ffs/mtp/ep1";
+constexpr char FFS_MTP_EP_OUT[] = "/dev/usb-ffs/mtp/ep2";
+constexpr char FFS_MTP_EP_INTR[] = "/dev/usb-ffs/mtp/ep3";
+
+constexpr int MAX_PACKET_SIZE_FS = 64;
+constexpr int MAX_PACKET_SIZE_HS = 512;
+constexpr int MAX_PACKET_SIZE_SS = 1024;
+
+// Must be divisible by all max packet size values
+constexpr int MAX_FILE_CHUNK_SIZE = 3145728;
+
+// Safe values since some devices cannot handle large DMAs
+// To get good performance, override these with
+// higher values per device using the properties
+// sys.usb.ffs.max_read and sys.usb.ffs.max_write
+constexpr int USB_FFS_MAX_WRITE = 32768;
+constexpr int USB_FFS_MAX_READ = 32768;
+
+constexpr unsigned int MAX_MTP_FILE_SIZE = 0xFFFFFFFF;
+
+struct func_desc {
+    struct usb_interface_descriptor intf;
+    struct usb_endpoint_descriptor_no_audio sink;
+    struct usb_endpoint_descriptor_no_audio source;
+    struct usb_endpoint_descriptor_no_audio intr;
+} __attribute__((packed));
+
+struct ss_func_desc {
+    struct usb_interface_descriptor intf;
+    struct usb_endpoint_descriptor_no_audio sink;
+    struct usb_ss_ep_comp_descriptor sink_comp;
+    struct usb_endpoint_descriptor_no_audio source;
+    struct usb_ss_ep_comp_descriptor source_comp;
+    struct usb_endpoint_descriptor_no_audio intr;
+    struct usb_ss_ep_comp_descriptor intr_comp;
+} __attribute__((packed));
+
+struct desc_v1 {
+    struct usb_functionfs_descs_head_v1 {
+        __le32 magic;
+        __le32 length;
+        __le32 fs_count;
+        __le32 hs_count;
+    } __attribute__((packed)) header;
+    struct func_desc fs_descs, hs_descs;
+} __attribute__((packed));
+
+struct desc_v2 {
+    struct usb_functionfs_descs_head_v2 header;
+    // The rest of the structure depends on the flags in the header.
+    __le32 fs_count;
+    __le32 hs_count;
+    __le32 ss_count;
+    struct func_desc fs_descs, hs_descs;
+    struct ss_func_desc ss_descs;
+} __attribute__((packed));
+
+const struct usb_interface_descriptor mtp_interface_desc = {
+    .bLength = USB_DT_INTERFACE_SIZE,
+    .bDescriptorType = USB_DT_INTERFACE,
+    .bInterfaceNumber = 0,
+    .bNumEndpoints = 3,
+    .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+    .bInterfaceSubClass = 1,
+    .bInterfaceProtocol = 1,
+};
+
+const struct usb_interface_descriptor ptp_interface_desc = {
+    .bLength = USB_DT_INTERFACE_SIZE,
+    .bDescriptorType = USB_DT_INTERFACE,
+    .bInterfaceNumber = 0,
+    .bNumEndpoints = 3,
+    .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+    .bInterfaceSubClass = 1,
+    .bInterfaceProtocol = 1,
+};
+
+const struct usb_endpoint_descriptor_no_audio fs_sink = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 1 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+};
+
+const struct usb_endpoint_descriptor_no_audio fs_source = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 2 | USB_DIR_OUT,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+};
+
+const struct usb_endpoint_descriptor_no_audio fs_intr = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 3 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_INT,
+    .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+    .bInterval = 6,
+};
+
+const struct usb_endpoint_descriptor_no_audio hs_sink = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 1 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+};
+
+const struct usb_endpoint_descriptor_no_audio hs_source = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 2 | USB_DIR_OUT,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+};
+
+const struct usb_endpoint_descriptor_no_audio hs_intr = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 3 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_INT,
+    .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+    .bInterval = 6,
+};
+
+const struct usb_endpoint_descriptor_no_audio ss_sink = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 1 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_SS,
+};
+
+const struct usb_endpoint_descriptor_no_audio ss_source = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 2 | USB_DIR_OUT,
+    .bmAttributes = USB_ENDPOINT_XFER_BULK,
+    .wMaxPacketSize = MAX_PACKET_SIZE_SS,
+};
+
+const struct usb_endpoint_descriptor_no_audio ss_intr = {
+    .bLength = USB_DT_ENDPOINT_SIZE,
+    .bDescriptorType = USB_DT_ENDPOINT,
+    .bEndpointAddress = 3 | USB_DIR_IN,
+    .bmAttributes = USB_ENDPOINT_XFER_INT,
+    .wMaxPacketSize = MAX_PACKET_SIZE_SS,
+    .bInterval = 6,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_sink_comp = {
+    .bLength = sizeof(ss_sink_comp),
+    .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+    .bMaxBurst = 2,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_source_comp = {
+    .bLength = sizeof(ss_source_comp),
+    .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+    .bMaxBurst = 2,
+};
+
+const struct usb_ss_ep_comp_descriptor ss_intr_comp = {
+    .bLength = sizeof(ss_intr_comp),
+    .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+};
+
+const struct func_desc mtp_fs_descriptors = {
+    .intf = mtp_interface_desc,
+    .sink = fs_sink,
+    .source = fs_source,
+    .intr = fs_intr,
+};
+
+const struct func_desc mtp_hs_descriptors = {
+    .intf = mtp_interface_desc,
+    .sink = hs_sink,
+    .source = hs_source,
+    .intr = hs_intr,
+};
+
+const struct ss_func_desc mtp_ss_descriptors = {
+    .intf = mtp_interface_desc,
+    .sink = ss_sink,
+    .sink_comp = ss_sink_comp,
+    .source = ss_source,
+    .source_comp = ss_source_comp,
+    .intr = ss_intr,
+    .intr_comp = ss_intr_comp,
+};
+
+const struct func_desc ptp_fs_descriptors = {
+    .intf = ptp_interface_desc,
+    .sink = fs_sink,
+    .source = fs_source,
+    .intr = fs_intr,
+};
+
+const struct func_desc ptp_hs_descriptors = {
+    .intf = ptp_interface_desc,
+    .sink = hs_sink,
+    .source = hs_source,
+    .intr = hs_intr,
+};
+
+const struct ss_func_desc ptp_ss_descriptors = {
+    .intf = ptp_interface_desc,
+    .sink = ss_sink,
+    .sink_comp = ss_sink_comp,
+    .source = ss_source,
+    .source_comp = ss_source_comp,
+    .intr = ss_intr,
+    .intr_comp = ss_intr_comp,
+};
+
+const struct {
+    struct usb_functionfs_strings_head header;
+} __attribute__((packed)) strings = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+        .length = cpu_to_le32(sizeof(strings)),
+        .str_count = cpu_to_le32(0),
+        .lang_count = cpu_to_le32(0),
+    },
+};
+
+} // anonymous namespace
+
+namespace android {
+
+MtpFfsHandle::MtpFfsHandle() :
+    mMaxWrite(USB_FFS_MAX_WRITE),
+    mMaxRead(USB_FFS_MAX_READ) {}
+
+MtpFfsHandle::~MtpFfsHandle() {}
+
+void MtpFfsHandle::closeEndpoints() {
+    mIntr.reset();
+    mBulkIn.reset();
+    mBulkOut.reset();
+}
+
+bool MtpFfsHandle::initFunctionfs() {
+    ssize_t ret;
+    struct desc_v1 v1_descriptor;
+    struct desc_v2 v2_descriptor;
+
+    v2_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2);
+    v2_descriptor.header.length = cpu_to_le32(sizeof(v2_descriptor));
+    v2_descriptor.header.flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC |
+                                 FUNCTIONFS_HAS_SS_DESC;
+    v2_descriptor.fs_count = 4;
+    v2_descriptor.hs_count = 4;
+    v2_descriptor.ss_count = 7;
+    v2_descriptor.fs_descs = mPtp ? ptp_fs_descriptors : mtp_fs_descriptors;
+    v2_descriptor.hs_descs = mPtp ? ptp_hs_descriptors : mtp_hs_descriptors;
+    v2_descriptor.ss_descs = mPtp ? ptp_ss_descriptors : mtp_ss_descriptors;
+
+    if (mControl < 0) { // might have already done this before
+        mControl.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP0, O_RDWR)));
+        if (mControl < 0) {
+            PLOG(ERROR) << FFS_MTP_EP0 << ": cannot open control endpoint";
+            goto err;
+        }
+
+        ret = TEMP_FAILURE_RETRY(::write(mControl, &v2_descriptor, sizeof(v2_descriptor)));
+        if (ret < 0) {
+            v1_descriptor.header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC);
+            v1_descriptor.header.length = cpu_to_le32(sizeof(v1_descriptor));
+            v1_descriptor.header.fs_count = 4;
+            v1_descriptor.header.hs_count = 4;
+            v1_descriptor.fs_descs = mPtp ? ptp_fs_descriptors : mtp_fs_descriptors;
+            v1_descriptor.hs_descs = mPtp ? ptp_hs_descriptors : mtp_hs_descriptors;
+            PLOG(ERROR) << FFS_MTP_EP0 << "Switching to V1 descriptor format";
+            ret = TEMP_FAILURE_RETRY(::write(mControl, &v1_descriptor, sizeof(v1_descriptor)));
+            if (ret < 0) {
+                PLOG(ERROR) << FFS_MTP_EP0 << "Writing descriptors failed";
+                goto err;
+            }
+        }
+        ret = TEMP_FAILURE_RETRY(::write(mControl, &strings, sizeof(strings)));
+        if (ret < 0) {
+            PLOG(ERROR) << FFS_MTP_EP0 << "Writing strings failed";
+            goto err;
+        }
+    }
+    if (mBulkIn > -1 || mBulkOut > -1 || mIntr > -1)
+        LOG(WARNING) << "Endpoints were not closed before configure!";
+
+    mBulkIn.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_IN, O_RDWR)));
+    if (mBulkIn < 0) {
+        PLOG(ERROR) << FFS_MTP_EP_IN << ": cannot open bulk in ep";
+        goto err;
+    }
+
+    mBulkOut.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_OUT, O_RDWR)));
+    if (mBulkOut < 0) {
+        PLOG(ERROR) << FFS_MTP_EP_OUT << ": cannot open bulk out ep";
+        goto err;
+    }
+
+    mIntr.reset(TEMP_FAILURE_RETRY(open(FFS_MTP_EP_INTR, O_RDWR)));
+    if (mIntr < 0) {
+        PLOG(ERROR) << FFS_MTP_EP0 << ": cannot open intr ep";
+        goto err;
+    }
+
+    return true;
+
+err:
+    closeEndpoints();
+    closeConfig();
+    return false;
+}
+
+void MtpFfsHandle::closeConfig() {
+    mControl.reset();
+}
+
+int MtpFfsHandle::writeHandle(int fd, const void* data, int len) {
+    LOG(VERBOSE) << "MTP about to write fd = " << fd << ", len=" << len;
+    int ret = 0;
+    const char* buf = static_cast<const char*>(data);
+    while (len > 0) {
+        int write_len = std::min(mMaxWrite, len);
+        int n = TEMP_FAILURE_RETRY(::write(fd, buf, write_len));
+
+        if (n < 0) {
+            PLOG(ERROR) << "write ERROR: fd = " << fd << ", n = " << n;
+            return -1;
+        } else if (n < write_len) {
+            errno = EIO;
+            PLOG(ERROR) << "less written than expected";
+            return -1;
+        }
+        buf += n;
+        len -= n;
+        ret += n;
+    }
+    return ret;
+}
+
+int MtpFfsHandle::readHandle(int fd, void* data, int len) {
+    LOG(VERBOSE) << "MTP about to read fd = " << fd << ", len=" << len;
+    int ret = 0;
+    char* buf = static_cast<char*>(data);
+    while (len > 0) {
+        int read_len = std::min(mMaxRead, len);
+        int n = TEMP_FAILURE_RETRY(::read(fd, buf, read_len));
+        if (n < 0) {
+            PLOG(ERROR) << "read ERROR: fd = " << fd << ", n = " << n;
+            return -1;
+        }
+        ret += n;
+        if (n < read_len) // done reading early
+            break;
+        buf += n;
+        len -= n;
+    }
+    return ret;
+}
+
+int MtpFfsHandle::spliceReadHandle(int fd, int pipe_out, int len) {
+    LOG(VERBOSE) << "MTP about to splice read fd = " << fd << ", len=" << len;
+    int ret = 0;
+    loff_t dummyoff;
+    while (len > 0) {
+        int read_len = std::min(mMaxRead, len);
+        dummyoff = 0;
+        int n = TEMP_FAILURE_RETRY(splice(fd, &dummyoff, pipe_out, nullptr, read_len, 0));
+        if (n < 0) {
+            PLOG(ERROR) << "splice read ERROR: fd = " << fd << ", n = " << n;
+            return -1;
+        }
+        ret += n;
+        if (n < read_len) // done reading early
+            break;
+        len -= n;
+    }
+    return ret;
+}
+
+int MtpFfsHandle::read(void* data, int len) {
+    return readHandle(mBulkOut, data, len);
+}
+
+int MtpFfsHandle::write(const void* data, int len) {
+    return writeHandle(mBulkIn, data, len);
+}
+
+int MtpFfsHandle::start() {
+    mLock.lock();
+    return 0;
+}
+
+int MtpFfsHandle::configure(bool usePtp) {
+    // Wait till previous server invocation has closed
+    std::lock_guard<std::mutex> lk(mLock);
+
+    // If ptp is changed, the configuration must be rewritten
+    if (mPtp != usePtp) {
+        closeEndpoints();
+        closeConfig();
+    }
+    mPtp = usePtp;
+
+    if (!initFunctionfs()) {
+        return -1;
+    }
+
+    // Get device specific r/w size
+    mMaxWrite = android::base::GetIntProperty("sys.usb.ffs.max_write", 0);
+    mMaxRead = android::base::GetIntProperty("sys.usb.ffs.max_read", 0);
+    if (!mMaxWrite)
+        mMaxWrite = USB_FFS_MAX_WRITE;
+    if (!mMaxRead)
+        mMaxRead = USB_FFS_MAX_READ;
+    return 0;
+}
+
+void MtpFfsHandle::close() {
+    closeEndpoints();
+    mLock.unlock();
+}
+
+/* Read from USB and write to a local file. */
+int MtpFfsHandle::receiveFile(mtp_file_range mfr) {
+    // When receiving files, the incoming length is given in 32 bits.
+    // A >4G file is given as 0xFFFFFFFF
+    uint32_t file_length = mfr.length;
+    uint64_t offset = lseek(mfr.fd, 0, SEEK_CUR);
+
+    int buf1_len = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
+    std::vector<char> buf1(buf1_len);
+    char* data = buf1.data();
+
+    // If necessary, allocate a second buffer for background r/w
+    int buf2_len = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE),
+            file_length - MAX_FILE_CHUNK_SIZE);
+    std::vector<char> buf2(std::max(0, buf2_len));
+    char *data2 = buf2.data();
+
+    struct aiocb aio;
+    aio.aio_fildes = mfr.fd;
+    aio.aio_buf = nullptr;
+    struct aiocb *aiol[] = {&aio};
+    int ret;
+    size_t length;
+    bool read = false;
+    bool write = false;
+
+    posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+    // Break down the file into pieces that fit in buffers
+    while (file_length > 0 || write) {
+        if (file_length > 0) {
+            length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length);
+
+            // Read data from USB
+            if ((ret = readHandle(mBulkOut, data, length)) == -1) {
+                return -1;
+            }
+
+            if (file_length != MAX_MTP_FILE_SIZE && ret < static_cast<int>(length)) {
+                errno = EIO;
+                return -1;
+            }
+            read = true;
+        }
+
+        if (write) {
+            // get the return status of the last write request
+            aio_suspend(aiol, 1, nullptr);
+
+            int written = aio_return(&aio);
+            if (written == -1) {
+                errno = aio_error(&aio);
+                return -1;
+            }
+            if (static_cast<size_t>(written) < aio.aio_nbytes) {
+                errno = EIO;
+                return -1;
+            }
+            write = false;
+        }
+
+        if (read) {
+            // Enqueue a new write request
+            aio.aio_buf = data;
+            aio.aio_sink = mfr.fd;
+            aio.aio_offset = offset;
+            aio.aio_nbytes = ret;
+            aio_write(&aio);
+
+            if (file_length == MAX_MTP_FILE_SIZE) {
+                // For larger files, receive until a short packet is received.
+                if (static_cast<size_t>(ret) < length) {
+                    file_length = 0;
+                }
+            } else {
+                file_length -= ret;
+            }
+
+            offset += ret;
+            std::swap(data, data2);
+
+            write = true;
+            read = false;
+        }
+    }
+    return 0;
+}
+
+/* Read from a local file and send over USB. */
+int MtpFfsHandle::sendFile(mtp_file_range mfr) {
+    uint64_t file_length = mfr.length;
+    uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE),
+            file_length + sizeof(mtp_data_header));
+    uint64_t offset = 0;
+    struct usb_endpoint_descriptor mBulkIn_desc;
+    int packet_size;
+
+    if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&mBulkIn_desc))) {
+        PLOG(ERROR) << "Could not get FFS bulk-in descriptor";
+        packet_size = MAX_PACKET_SIZE_HS;
+    } else {
+        packet_size = mBulkIn_desc.wMaxPacketSize;
+    }
+
+    posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+
+    int init_read_len = packet_size - sizeof(mtp_data_header);
+    int buf1_len = std::max(static_cast<uint64_t>(packet_size), std::min(
+                  static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length - init_read_len));
+    std::vector<char> buf1(buf1_len);
+    char *data = buf1.data();
+
+    // If necessary, allocate a second buffer for background r/w
+    int buf2_len = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE),
+            file_length - MAX_FILE_CHUNK_SIZE - init_read_len);
+    std::vector<char> buf2(std::max(0, buf2_len));
+    char *data2 = buf2.data();
+
+    struct aiocb aio;
+    aio.aio_fildes = mfr.fd;
+    struct aiocb *aiol[] = {&aio};
+    int ret, length;
+    bool read = false;
+    bool write = false;
+
+    // Send the header data
+    mtp_data_header *header = reinterpret_cast<mtp_data_header*>(data);
+    header->length = __cpu_to_le32(given_length);
+    header->type = __cpu_to_le16(2); /* data packet */
+    header->command = __cpu_to_le16(mfr.command);
+    header->transaction_id = __cpu_to_le32(mfr.transaction_id);
+
+    // Some hosts don't support header/data separation even though MTP allows it
+    // Handle by filling first packet with initial file data
+    if (TEMP_FAILURE_RETRY(pread(mfr.fd, reinterpret_cast<char*>(data) +
+                    sizeof(mtp_data_header), init_read_len, offset))
+            != init_read_len) return -1;
+    file_length -= init_read_len;
+    offset += init_read_len;
+    if (writeHandle(mBulkIn, data, packet_size) == -1) return -1;
+    if (file_length == 0) return 0;
+
+    // Break down the file into pieces that fit in buffers
+    while(file_length > 0) {
+        if (read) {
+            // Wait for the previous read to finish
+            aio_suspend(aiol, 1, nullptr);
+            ret = aio_return(&aio);
+            if (ret == -1) {
+                errno = aio_error(&aio);
+                return -1;
+            }
+            if (static_cast<size_t>(ret) < aio.aio_nbytes) {
+                errno = EIO;
+                return -1;
+            }
+
+            file_length -= ret;
+            offset += ret;
+            std::swap(data, data2);
+            read = false;
+            write = true;
+        }
+
+        if (file_length > 0) {
+            length = std::min((uint64_t) MAX_FILE_CHUNK_SIZE, file_length);
+            // Queue up another read
+            aio.aio_buf = data;
+            aio.aio_offset = offset;
+            aio.aio_nbytes = length;
+            aio_read(&aio);
+            read = true;
+        }
+
+        if (write) {
+            if (writeHandle(mBulkIn, data2, ret) == -1)
+                return -1;
+            write = false;
+        }
+    }
+
+    if (given_length == MAX_MTP_FILE_SIZE && ret % packet_size == 0) {
+        // If the last packet wasn't short, send a final empty packet
+        if (writeHandle(mBulkIn, data, 0) == -1) return -1;
+    }
+
+    return 0;
+}
+
+int MtpFfsHandle::sendEvent(mtp_event me) {
+    unsigned length = me.length;
+    int ret = writeHandle(mIntr, me.data, length);
+    return static_cast<unsigned>(ret) == length ? 0 : -1;
+}
+
+} // namespace android
+
+IMtpHandle *get_ffs_handle() {
+    return new android::MtpFfsHandle();
+}
+
diff --git a/media/mtp/MtpFfsHandle.h b/media/mtp/MtpFfsHandle.h
new file mode 100644
index 0000000..9cd4dcf
--- /dev/null
+++ b/media/mtp/MtpFfsHandle.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _MTP_FFS_HANDLE_H
+#define _MTP_FFS_HANDLE_H
+
+#include <android-base/unique_fd.h>
+#include <IMtpHandle.h>
+
+namespace android {
+
+class MtpFfsHandleTest;
+
+class MtpFfsHandle : public IMtpHandle {
+    friend class android::MtpFfsHandleTest;
+private:
+    int writeHandle(int fd, const void *data, int len);
+    int readHandle(int fd, void *data, int len);
+    int spliceReadHandle(int fd, int fd_out, int len);
+    bool initFunctionfs();
+    void closeConfig();
+    void closeEndpoints();
+
+    bool mPtp;
+
+    std::mutex mLock;
+
+    android::base::unique_fd mControl;
+    // "in" from the host's perspective => sink for mtp server
+    android::base::unique_fd mBulkIn;
+    // "out" from the host's perspective => source for mtp server
+    android::base::unique_fd mBulkOut;
+    android::base::unique_fd mIntr;
+
+    int mMaxWrite;
+    int mMaxRead;
+
+public:
+    int read(void *data, int len);
+    int write(const void *data, int len);
+
+    int receiveFile(mtp_file_range mfr);
+    int sendFile(mtp_file_range mfr);
+    int sendEvent(mtp_event me);
+
+    int start();
+    void close();
+
+    int configure(bool ptp);
+
+    MtpFfsHandle();
+    ~MtpFfsHandle();
+};
+
+struct mtp_data_header {
+    /* length of packet, including this header */
+    __le32 length;
+    /* container type (2 for data packet) */
+    __le16 type;
+    /* MTP command code */
+    __le16 command;
+    /* MTP transaction ID */
+    __le32 transaction_id;
+};
+
+} // namespace android
+
+#endif // _MTP_FF_HANDLE_H
+
diff --git a/media/mtp/MtpPacket.h b/media/mtp/MtpPacket.h
index 4da53bf..d47c91d 100644
--- a/media/mtp/MtpPacket.h
+++ b/media/mtp/MtpPacket.h
@@ -17,6 +17,8 @@
 #ifndef _MTP_PACKET_H
 #define _MTP_PACKET_H
 
+#include <android-base/macros.h>
+
 #include "MtpTypes.h"
 
 struct usb_device;
@@ -66,6 +68,8 @@
     uint32_t            getUInt32(int offset) const;
     void                putUInt16(int offset, uint16_t value);
     void                putUInt32(int offset, uint32_t value);
+
+    DISALLOW_COPY_AND_ASSIGN(MtpPacket);
 };
 
 }; // namespace android
diff --git a/media/mtp/MtpRequestPacket.cpp b/media/mtp/MtpRequestPacket.cpp
index 471967f..e0e86a9 100644
--- a/media/mtp/MtpRequestPacket.cpp
+++ b/media/mtp/MtpRequestPacket.cpp
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <fcntl.h>
 
+#include "IMtpHandle.h"
 #include "MtpRequestPacket.h"
 
 #include <usbhost/usbhost.h>
@@ -36,8 +37,8 @@
 }
 
 #ifdef MTP_DEVICE
-int MtpRequestPacket::read(int fd) {
-    int ret = ::read(fd, mBuffer, mBufferSize);
+int MtpRequestPacket::read(IMtpHandle *h) {
+    int ret = h->read(mBuffer, mBufferSize);
     if (ret < 0) {
         // file read error
         return ret;
diff --git a/media/mtp/MtpRequestPacket.h b/media/mtp/MtpRequestPacket.h
index 79b798d..d1dc0ff 100644
--- a/media/mtp/MtpRequestPacket.h
+++ b/media/mtp/MtpRequestPacket.h
@@ -20,6 +20,7 @@
 #include "MtpPacket.h"
 #include "mtp.h"
 
+class IMtpHandle;
 struct usb_request;
 
 namespace android {
@@ -31,8 +32,8 @@
     virtual             ~MtpRequestPacket();
 
 #ifdef MTP_DEVICE
-    // fill our buffer with data from the given file descriptor
-    int                 read(int fd);
+    // fill our buffer with data from the given usb handle
+    int                 read(IMtpHandle *h);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpResponsePacket.cpp b/media/mtp/MtpResponsePacket.cpp
index c2b41e4..f186b37 100644
--- a/media/mtp/MtpResponsePacket.cpp
+++ b/media/mtp/MtpResponsePacket.cpp
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <fcntl.h>
 
+#include "IMtpHandle.h"
 #include "MtpResponsePacket.h"
 
 #include <usbhost/usbhost.h>
@@ -35,10 +36,10 @@
 }
 
 #ifdef MTP_DEVICE
-int MtpResponsePacket::write(int fd) {
+int MtpResponsePacket::write(IMtpHandle *h) {
     putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
     putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_RESPONSE);
-    int ret = ::write(fd, mBuffer, mPacketSize);
+    int ret = h->write(mBuffer, mPacketSize);
     return (ret < 0 ? ret : 0);
 }
 #endif
diff --git a/media/mtp/MtpResponsePacket.h b/media/mtp/MtpResponsePacket.h
index 592ad4a..29a72ba 100644
--- a/media/mtp/MtpResponsePacket.h
+++ b/media/mtp/MtpResponsePacket.h
@@ -20,6 +20,8 @@
 #include "MtpPacket.h"
 #include "mtp.h"
 
+class IMtpHandle;
+
 namespace android {
 
 class MtpResponsePacket : public MtpPacket {
@@ -29,8 +31,8 @@
     virtual             ~MtpResponsePacket();
 
 #ifdef MTP_DEVICE
-    // write our data to the given file descriptor
-    int                 write(int fd);
+    // write our data to the given usb handle
+    int                 write(IMtpHandle *h);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index e39dcdd..2a96ac9 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -14,18 +14,18 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
+#include <chrono>
+#include <cutils/properties.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
-#include <sys/ioctl.h>
 #include <sys/stat.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <errno.h>
 #include <sys/stat.h>
-#include <dirent.h>
-
-#include <cutils/properties.h>
 
 #define LOG_TAG "MtpServer"
 
@@ -37,8 +37,6 @@
 #include "MtpStorage.h"
 #include "MtpStringBuffer.h"
 
-#include <linux/usb/f_mtp.h>
-
 namespace android {
 
 static const MtpOperationCode kSupportedOperationCodes[] = {
@@ -97,10 +95,9 @@
     MTP_EVENT_DEVICE_PROP_CHANGED,
 };
 
-MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
+MtpServer::MtpServer(MtpDatabase* database, bool ptp,
                     int fileGroup, int filePerm, int directoryPerm)
-    :   mFD(fd),
-        mDatabase(database),
+    :   mDatabase(database),
         mPtp(ptp),
         mFileGroup(fileGroup),
         mFilePermission(filePerm),
@@ -116,6 +113,21 @@
 MtpServer::~MtpServer() {
 }
 
+IMtpHandle* MtpServer::sHandle = nullptr;
+
+int MtpServer::configure(bool usePtp) {
+    if (sHandle == nullptr) {
+        bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
+        sHandle = ffs_ok ? get_ffs_handle() : get_mtp_handle();
+    }
+
+    int ret = sHandle->configure(usePtp);
+    if (ret) ALOGE("Failed to configure MTP driver!");
+    else android::base::SetProperty("sys.usb.ffs.mtp.ready", "1");
+
+    return ret;
+}
+
 void MtpServer::addStorage(MtpStorage* storage) {
     Mutex::Autolock autoLock(mMutex);
 
@@ -143,24 +155,30 @@
         if (storage->getStorageID() == id)
             return storage;
     }
-    return NULL;
+    return nullptr;
 }
 
 bool MtpServer::hasStorage(MtpStorageID id) {
     if (id == 0 || id == 0xFFFFFFFF)
         return mStorages.size() > 0;
-    return (getStorage(id) != NULL);
+    return (getStorage(id) != nullptr);
 }
 
 void MtpServer::run() {
-    int fd = mFD;
+    if (!sHandle) {
+        ALOGE("MtpServer was never configured!");
+        return;
+    }
 
-    ALOGV("MtpServer::run fd: %d\n", fd);
+    if (sHandle->start()) {
+        ALOGE("Failed to start usb driver!");
+        return;
+    }
 
     while (1) {
-        int ret = mRequest.read(fd);
+        int ret = mRequest.read(sHandle);
         if (ret < 0) {
-            ALOGV("request read returned %d, errno: %d", ret, errno);
+            ALOGE("request read returned %d, errno: %d", ret, errno);
             if (errno == ECANCELED) {
                 // return to top of loop and wait for next command
                 continue;
@@ -171,15 +189,13 @@
         MtpTransactionID transaction = mRequest.getTransactionID();
 
         ALOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
-        mRequest.dump();
-
         // FIXME need to generalize this
         bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
                     || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
                     || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
                     || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
         if (dataIn) {
-            int ret = mData.read(fd);
+            int ret = mData.read(sHandle);
             if (ret < 0) {
                 ALOGE("data read returned %d, errno: %d", ret, errno);
                 if (errno == ECANCELED) {
@@ -189,7 +205,6 @@
                 break;
             }
             ALOGV("received data:");
-            mData.dump();
         } else {
             mData.reset();
         }
@@ -199,8 +214,7 @@
                 mData.setOperationCode(operation);
                 mData.setTransactionID(transaction);
                 ALOGV("sending data:");
-                mData.dump();
-                ret = mData.write(fd);
+                ret = mData.write(sHandle);
                 if (ret < 0) {
                     ALOGE("request write returned %d, errno: %d", ret, errno);
                     if (errno == ECANCELED) {
@@ -213,9 +227,8 @@
 
             mResponse.setTransactionID(transaction);
             ALOGV("sending response %04X", mResponse.getResponseCode());
-            ret = mResponse.write(fd);
+            ret = mResponse.write(sHandle);
             const int savedErrno = errno;
-            mResponse.dump();
             if (ret < 0) {
                 ALOGE("request write returned %d, errno: %d", ret, errno);
                 if (savedErrno == ECANCELED) {
@@ -240,8 +253,8 @@
 
     if (mSessionOpen)
         mDatabase->sessionEnded();
-    close(fd);
-    mFD = -1;
+
+    sHandle->close();
 }
 
 void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
@@ -274,8 +287,8 @@
         mEvent.setEventCode(code);
         mEvent.setTransactionID(mRequest.getTransactionID());
         mEvent.setParameter(1, param1);
-        int ret = mEvent.write(mFD);
-        ALOGV("mEvent.write returned %d\n", ret);
+        if (mEvent.write(sHandle))
+            ALOGE("Mtp send event failed: %s", strerror(errno));
     }
 }
 
@@ -291,7 +304,7 @@
         ObjectEdit* edit = mObjectEditList[i];
         if (edit->mHandle == handle) return edit;
     }
-    return NULL;
+    return nullptr;
 }
 
 void MtpServer::removeEditObject(MtpObjectHandle handle) {
@@ -775,6 +788,8 @@
     if (result != MTP_RESPONSE_OK)
         return result;
 
+    auto start = std::chrono::steady_clock::now();
+
     const char* filePath = (const char *)pathBuf;
     mtp_file_range  mfr;
     mfr.fd = open(filePath, O_RDONLY);
@@ -787,8 +802,9 @@
     mfr.transaction_id = mRequest.getTransactionID();
 
     // then transfer the file
-    int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
+    int ret = sHandle->sendFile(mfr);
     if (ret < 0) {
+        ALOGE("Mtp send file got error %s", strerror(errno));
         if (errno == ECANCELED) {
             result = MTP_RESPONSE_TRANSACTION_CANCELLED;
         } else {
@@ -798,7 +814,13 @@
         result = MTP_RESPONSE_OK;
     }
 
-    ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
+    auto end = std::chrono::steady_clock::now();
+    std::chrono::duration<double> diff = end - start;
+    struct stat sstat;
+    fstat(mfr.fd, &sstat);
+    uint64_t finalsize = sstat.st_size;
+    ALOGV("Sent a file over MTP. Time: %f s, Size: %" PRIu64 ", Rate: %f bytes/s",
+            diff.count(), finalsize, ((double) finalsize) / diff.count());
     close(mfr.fd);
     return result;
 }
@@ -813,7 +835,7 @@
         // send data
         mData.setOperationCode(mRequest.getOperationCode());
         mData.setTransactionID(mRequest.getTransactionID());
-        mData.writeData(mFD, thumb, thumbSize);
+        mData.writeData(sHandle, thumb, thumbSize);
         free(thumb);
         return MTP_RESPONSE_OK;
     } else {
@@ -867,7 +889,7 @@
     mResponse.setParameter(1, length);
 
     // transfer the file
-    int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
+    int ret = sHandle->sendFile(mfr);
     ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
     result = MTP_RESPONSE_OK;
     if (ret < 0) {
@@ -995,6 +1017,8 @@
     int ret, initialData;
     bool isCanceled = false;
 
+    auto start = std::chrono::steady_clock::now();
+
     if (mSendObjectHandle == kInvalidObjectHandle) {
         ALOGE("Expected SendObjectInfo before SendObject");
         result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
@@ -1002,7 +1026,7 @@
     }
 
     // read the header, and possibly some data
-    ret = mData.read(mFD);
+    ret = mData.read(sHandle);
     if (ret < MTP_CONTAINER_HEADER_SIZE) {
         result = MTP_RESPONSE_GENERAL_ERROR;
         goto done;
@@ -1038,19 +1062,19 @@
                 mfr.length = mSendObjectFileSize - initialData;
             }
 
-            ALOGV("receiving %s\n", (const char *)mSendObjectFilePath);
             // transfer the file
-            ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+            ret = sHandle->receiveFile(mfr);
             if ((ret < 0) && (errno == ECANCELED)) {
                 isCanceled = true;
             }
-
-            ALOGV("MTP_RECEIVE_FILE returned %d\n", ret);
         }
     }
+    struct stat sstat;
+    fstat(mfr.fd, &sstat);
     close(mfr.fd);
 
     if (ret < 0) {
+        ALOGE("Mtp receive file got error %s", strerror(errno));
         unlink(mSendObjectFilePath);
         if (isCanceled)
             result = MTP_RESPONSE_TRANSACTION_CANCELLED;
@@ -1066,6 +1090,12 @@
             result == MTP_RESPONSE_OK);
     mSendObjectHandle = kInvalidObjectHandle;
     mSendObjectFormat = 0;
+
+    auto end = std::chrono::steady_clock::now();
+    std::chrono::duration<double> diff = end - start;
+    uint64_t finalsize = sstat.st_size;
+    ALOGV("Got a file over MTP. Time: %fs, Size: %" PRIu64 ", Rate: %f bytes/s",
+            diff.count(), finalsize, ((double) finalsize) / diff.count());
     return result;
 }
 
@@ -1209,7 +1239,7 @@
     ALOGV("receiving partial %s %" PRIu64 " %" PRIu32, filePath, offset, length);
 
     // read the header, and possibly some data
-    int ret = mData.read(mFD);
+    int ret = mData.read(sHandle);
     if (ret < MTP_CONTAINER_HEADER_SIZE)
         return MTP_RESPONSE_GENERAL_ERROR;
     int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
@@ -1231,11 +1261,10 @@
             mfr.length = length;
 
             // transfer the file
-            ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
+            ret = sHandle->receiveFile(mfr);
             if ((ret < 0) && (errno == ECANCELED)) {
                 isCanceled = true;
             }
-            ALOGV("MTP_RECEIVE_FILE returned %d", ret);
         }
     }
     if (ret < 0) {
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index b3a11e0..c80e6a8 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -23,8 +23,12 @@
 #include "MtpEventPacket.h"
 #include "mtp.h"
 #include "MtpUtils.h"
+#include "IMtpHandle.h"
 
 #include <utils/threads.h>
+#include <queue>
+#include <memory>
+#include <mutex>
 
 namespace android {
 
@@ -34,9 +38,6 @@
 class MtpServer {
 
 private:
-    // file descriptor for MTP kernel driver
-    int                 mFD;
-
     MtpDatabase*        mDatabase;
 
     // appear as a PTP device
@@ -56,10 +57,13 @@
     MtpRequestPacket    mRequest;
     MtpDataPacket       mData;
     MtpResponsePacket   mResponse;
+
     MtpEventPacket      mEvent;
 
     MtpStorageList      mStorages;
 
+    static IMtpHandle*  sHandle;
+
     // handle for new object, set by SendObjectInfo and used by SendObject
     MtpObjectHandle     mSendObjectHandle;
     MtpObjectFormat     mSendObjectFormat;
@@ -90,7 +94,7 @@
     Vector<ObjectEdit*>  mObjectEditList;
 
 public:
-                        MtpServer(int fd, MtpDatabase* database, bool ptp,
+                        MtpServer(MtpDatabase* database, bool ptp,
                                     int fileGroup, int filePerm, int directoryPerm);
     virtual             ~MtpServer();
 
@@ -100,6 +104,7 @@
     void                addStorage(MtpStorage* storage);
     void                removeStorage(MtpStorage* storage);
 
+    static int          configure(bool usePtp);
     void                run();
 
     void                sendObjectAdded(MtpObjectHandle handle);
diff --git a/media/mtp/tests/Android.mk b/media/mtp/tests/Android.mk
new file mode 100644
index 0000000..ace0d40
--- /dev/null
+++ b/media/mtp/tests/Android.mk
@@ -0,0 +1,51 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_MODULE := mtp_ffs_handle_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+	MtpFfsHandle_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbase \
+	libcutils \
+	libmedia \
+	libmtp \
+	libutils \
+	liblog
+
+LOCAL_C_INCLUDES := \
+	frameworks/av/media/mtp \
+
+LOCAL_CFLAGS += -Werror -Wall
+
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_MODULE := async_io_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+	AsyncIO_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+	libbase \
+	libcutils \
+	libmedia \
+	libmtp \
+	libutils \
+	liblog
+
+LOCAL_C_INCLUDES := \
+	frameworks/av/media/mtp \
+
+LOCAL_CFLAGS += -Werror -Wall
+
+include $(BUILD_NATIVE_TEST)
diff --git a/media/mtp/tests/AsyncIO_test.cpp b/media/mtp/tests/AsyncIO_test.cpp
new file mode 100644
index 0000000..b5f4538
--- /dev/null
+++ b/media/mtp/tests/AsyncIO_test.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "AsyncIO_test.cpp"
+
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <string>
+#include <unistd.h>
+#include <utils/Log.h>
+
+#include "AsyncIO.h"
+
+namespace android {
+
+constexpr int TEST_PACKET_SIZE = 512;
+constexpr int POOL_COUNT = 10;
+
+static const std::string dummyDataStr =
+    "/*\n * Copyright 2015 The Android Open Source Project\n *\n * Licensed un"
+    "der the Apache License, Version 2.0 (the \"License\");\n * you may not us"
+    "e this file except in compliance with the License.\n * You may obtain a c"
+    "opy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE"
+    "-2.0\n *\n * Unless required by applicable law or agreed to in writing, s"
+    "oftware\n * distributed under the License is distributed on an \"AS IS\" "
+    "BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express o"
+    "r implied.\n * Se";
+
+
+class AsyncIOTest : public ::testing::Test {
+protected:
+    TemporaryFile dummy_file;
+
+    AsyncIOTest() {}
+    ~AsyncIOTest() {}
+};
+
+TEST_F(AsyncIOTest, testRead) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    EXPECT_EQ(write(dummy_file.fd, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    struct aiocb aio;
+    struct aiocb *aiol[] = {&aio};
+    aio.aio_fildes = dummy_file.fd;
+    aio.aio_buf = buf;
+    aio.aio_offset = 0;
+    aio.aio_nbytes = TEST_PACKET_SIZE;
+
+    EXPECT_EQ(aio_read(&aio), 0);
+    EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+    EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(AsyncIOTest, testWrite) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    struct aiocb aio;
+    struct aiocb *aiol[] = {&aio};
+    aio.aio_fildes = dummy_file.fd;
+    aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
+    aio.aio_offset = 0;
+    aio.aio_nbytes = TEST_PACKET_SIZE;
+
+    EXPECT_EQ(aio_write(&aio), 0);
+    EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+    EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+    EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(AsyncIOTest, testError) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    struct aiocb aio;
+    struct aiocb *aiol[] = {&aio};
+    aio.aio_fildes = -1;
+    aio.aio_buf = const_cast<char*>(dummyDataStr.c_str());
+    aio.aio_offset = 0;
+    aio.aio_nbytes = TEST_PACKET_SIZE;
+
+    EXPECT_EQ(aio_write(&aio), 0);
+    EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+    EXPECT_EQ(aio_return(&aio), -1);
+    EXPECT_EQ(aio_error(&aio), EBADF);
+}
+
+TEST_F(AsyncIOTest, testSpliceRead) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    int pipeFd[2];
+    EXPECT_EQ(pipe(pipeFd), 0);
+    EXPECT_EQ(write(dummy_file.fd, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    struct aiocb aio;
+    struct aiocb *aiol[] = {&aio};
+    aio.aio_fildes = dummy_file.fd;
+    aio.aio_sink = pipeFd[1];
+    aio.aio_offset = 0;
+    aio.aio_nbytes = TEST_PACKET_SIZE;
+
+    EXPECT_EQ(aio_splice_read(&aio), 0);
+    EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+    EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+
+    EXPECT_EQ(read(pipeFd[0], buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(AsyncIOTest, testSpliceWrite) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    int pipeFd[2];
+    EXPECT_EQ(pipe(pipeFd), 0);
+    EXPECT_EQ(write(pipeFd[1], dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    struct aiocb aio;
+    struct aiocb *aiol[] = {&aio};
+    aio.aio_fildes = pipeFd[0];
+    aio.aio_sink = dummy_file.fd;
+    aio.aio_offset = 0;
+    aio.aio_nbytes = TEST_PACKET_SIZE;
+
+    EXPECT_EQ(aio_splice_write(&aio), 0);
+    EXPECT_EQ(aio_suspend(aiol, 1, nullptr), 0);
+    EXPECT_EQ(aio_return(&aio), TEST_PACKET_SIZE);
+    EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(AsyncIOTest, testPoolWrite) {
+    aio_pool_write_init();
+    char buf[TEST_PACKET_SIZE * POOL_COUNT + 1];
+    buf[TEST_PACKET_SIZE * POOL_COUNT] = '\0';
+
+    for (int i = 0; i < POOL_COUNT; i++) {
+        struct aiocb *aiop = new struct aiocb;
+        aiop->aio_fildes = dummy_file.fd;
+        aiop->aio_pool_buf = std::unique_ptr<char[]>(new char[TEST_PACKET_SIZE]);
+        memcpy(aiop->aio_pool_buf.get(), dummyDataStr.c_str(), TEST_PACKET_SIZE);
+        aiop->aio_offset = i * TEST_PACKET_SIZE;
+        aiop->aio_nbytes = TEST_PACKET_SIZE;
+        EXPECT_EQ(aio_pool_write(aiop), 0);
+    }
+    aio_pool_end();
+    EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE * POOL_COUNT), TEST_PACKET_SIZE * POOL_COUNT);
+
+    std::stringstream ss;
+    for (int i = 0; i < POOL_COUNT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_STREQ(buf, ss.str().c_str());
+}
+
+TEST_F(AsyncIOTest, testSplicePoolWrite) {
+    aio_pool_splice_init();
+    char buf[TEST_PACKET_SIZE * POOL_COUNT + 1];
+    buf[TEST_PACKET_SIZE * POOL_COUNT] = '\0';
+
+    for (int i = 0; i < POOL_COUNT; i++) {
+        int pipeFd[2];
+        EXPECT_EQ(pipe(pipeFd), 0);
+        EXPECT_EQ(write(pipeFd[1], dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+        struct aiocb *aiop = new struct aiocb;
+        aiop->aio_fildes = pipeFd[0];
+        aiop->aio_sink = dummy_file.fd;
+        aiop->aio_offset = i * TEST_PACKET_SIZE;
+        aiop->aio_nbytes = TEST_PACKET_SIZE;
+        EXPECT_EQ(aio_pool_write(aiop), 0);
+    }
+    aio_pool_end();
+    EXPECT_EQ(read(dummy_file.fd, buf, TEST_PACKET_SIZE * POOL_COUNT), TEST_PACKET_SIZE * POOL_COUNT);
+
+    std::stringstream ss;
+    for (int i = 0; i < POOL_COUNT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_STREQ(buf, ss.str().c_str());
+}
+
+} // namespace android
diff --git a/media/mtp/tests/MtpFfsHandle_test.cpp b/media/mtp/tests/MtpFfsHandle_test.cpp
new file mode 100644
index 0000000..b511041
--- /dev/null
+++ b/media/mtp/tests/MtpFfsHandle_test.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "MtpFfsHandle_test.cpp"
+
+#include <android-base/unique_fd.h>
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+#include <unistd.h>
+#include <utils/Log.h>
+
+#include "MtpFfsHandle.h"
+
+namespace android {
+
+constexpr int TEST_PACKET_SIZE = 512;
+constexpr int SMALL_MULT = 30;
+constexpr int MED_MULT = 510;
+
+static const std::string dummyDataStr =
+    "/*\n * Copyright 2015 The Android Open Source Project\n *\n * Licensed un"
+    "der the Apache License, Version 2.0 (the \"License\");\n * you may not us"
+    "e this file except in compliance with the License.\n * You may obtain a c"
+    "opy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE"
+    "-2.0\n *\n * Unless required by applicable law or agreed to in writing, s"
+    "oftware\n * distributed under the License is distributed on an \"AS IS\" "
+    "BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express o"
+    "r implied.\n * Se";
+
+class MtpFfsHandleTest : public ::testing::Test {
+protected:
+    std::unique_ptr<IMtpHandle> handle;
+
+    // Pipes for reading endpoint data
+    android::base::unique_fd bulk_in;
+    android::base::unique_fd bulk_out;
+    android::base::unique_fd intr;
+
+    TemporaryFile dummy_file;
+
+    MtpFfsHandleTest() {
+        int fd[2];
+        handle = std::unique_ptr<IMtpHandle>(get_ffs_handle());
+        MtpFfsHandle *ffs_handle = static_cast<MtpFfsHandle*>(handle.get());
+        EXPECT_TRUE(ffs_handle != NULL);
+
+        EXPECT_EQ(pipe(fd), 0);
+        EXPECT_EQ(fcntl(fd[0], F_SETPIPE_SZ, 1048576), 1048576);
+        bulk_in.reset(fd[0]);
+        ffs_handle->mBulkIn.reset(fd[1]);
+
+        EXPECT_EQ(pipe(fd), 0);
+        EXPECT_EQ(fcntl(fd[0], F_SETPIPE_SZ, 1048576), 1048576);
+        bulk_out.reset(fd[1]);
+        ffs_handle->mBulkOut.reset(fd[0]);
+
+        EXPECT_EQ(pipe(fd), 0);
+        intr.reset(fd[0]);
+        ffs_handle->mIntr.reset(fd[1]);
+    }
+
+    ~MtpFfsHandleTest() {}
+};
+
+TEST_F(MtpFfsHandleTest, testRead) {
+    EXPECT_EQ(write(bulk_out, dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    EXPECT_EQ(handle->read(buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(MtpFfsHandleTest, testWrite) {
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+    EXPECT_EQ(handle->write(dummyDataStr.c_str(), TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_EQ(read(bulk_in, buf, TEST_PACKET_SIZE), TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+TEST_F(MtpFfsHandleTest, testReceiveFileSmall) {
+    std::stringstream ss;
+    mtp_file_range mfr;
+    int size = TEST_PACKET_SIZE * SMALL_MULT;
+    char buf[size + 1];
+    buf[size] = '\0';
+
+    mfr.length = size;
+    mfr.fd = dummy_file.fd;
+    for (int i = 0; i < SMALL_MULT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_EQ(write(bulk_out, ss.str().c_str(), size), size);
+    EXPECT_EQ(handle->receiveFile(mfr), 0);
+
+    EXPECT_EQ(read(dummy_file.fd, buf, size), size);
+
+    EXPECT_STREQ(buf, ss.str().c_str());
+}
+
+TEST_F(MtpFfsHandleTest, testReceiveFileMed) {
+    std::stringstream ss;
+    mtp_file_range mfr;
+    int size = TEST_PACKET_SIZE * MED_MULT;
+    char buf[size + 1];
+    buf[size] = '\0';
+
+    mfr.length = size;
+    mfr.fd = dummy_file.fd;
+    for (int i = 0; i < MED_MULT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_EQ(write(bulk_out, ss.str().c_str(), size), size);
+    EXPECT_EQ(handle->receiveFile(mfr), 0);
+
+    EXPECT_EQ(read(dummy_file.fd, buf, size), size);
+
+    EXPECT_STREQ(buf, ss.str().c_str());
+}
+
+TEST_F(MtpFfsHandleTest, testSendFileSmall) {
+    std::stringstream ss;
+    mtp_file_range mfr;
+    mfr.command = 42;
+    mfr.transaction_id = 1337;
+    int size = TEST_PACKET_SIZE * SMALL_MULT;
+    char buf[size + sizeof(mtp_data_header) + 1];
+    buf[size + sizeof(mtp_data_header)] = '\0';
+
+    mfr.length = size;
+    mfr.fd = dummy_file.fd;
+    for (int i = 0; i < SMALL_MULT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_EQ(write(dummy_file.fd, ss.str().c_str(), size), size);
+    EXPECT_EQ(handle->sendFile(mfr), 0);
+
+    EXPECT_EQ(read(bulk_in, buf, size + sizeof(mtp_data_header)),
+            static_cast<long>(size + sizeof(mtp_data_header)));
+
+    struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(buf);
+    EXPECT_STREQ(buf + sizeof(mtp_data_header), ss.str().c_str());
+    EXPECT_EQ(header->length, static_cast<unsigned int>(size + sizeof(mtp_data_header)));
+    EXPECT_EQ(header->type, static_cast<unsigned int>(2));
+    EXPECT_EQ(header->command, static_cast<unsigned int>(42));
+    EXPECT_EQ(header->transaction_id, static_cast<unsigned int>(1337));
+}
+
+TEST_F(MtpFfsHandleTest, testSendFileMed) {
+    std::stringstream ss;
+    mtp_file_range mfr;
+    mfr.command = 42;
+    mfr.transaction_id = 1337;
+    int size = TEST_PACKET_SIZE * MED_MULT;
+    char buf[size + sizeof(mtp_data_header) + 1];
+    buf[size + sizeof(mtp_data_header)] = '\0';
+
+    mfr.length = size;
+    mfr.fd = dummy_file.fd;
+    for (int i = 0; i < MED_MULT; i++)
+        ss << dummyDataStr;
+
+    EXPECT_EQ(write(dummy_file.fd, ss.str().c_str(), size), size);
+    EXPECT_EQ(handle->sendFile(mfr), 0);
+
+    EXPECT_EQ(read(bulk_in, buf, size + sizeof(mtp_data_header)),
+            static_cast<long>(size + sizeof(mtp_data_header)));
+
+    struct mtp_data_header *header = reinterpret_cast<struct mtp_data_header*>(buf);
+    EXPECT_STREQ(buf + sizeof(mtp_data_header), ss.str().c_str());
+    EXPECT_EQ(header->length, static_cast<unsigned int>(size + sizeof(mtp_data_header)));
+    EXPECT_EQ(header->type, static_cast<unsigned int>(2));
+    EXPECT_EQ(header->command, static_cast<unsigned int>(42));
+    EXPECT_EQ(header->transaction_id, static_cast<unsigned int>(1337));
+}
+
+TEST_F(MtpFfsHandleTest, testSendEvent) {
+    struct mtp_event event;
+    event.length = TEST_PACKET_SIZE;
+    event.data = const_cast<char*>(dummyDataStr.c_str());
+    char buf[TEST_PACKET_SIZE + 1];
+    buf[TEST_PACKET_SIZE] = '\0';
+
+    handle->sendEvent(event);
+    read(intr, buf, TEST_PACKET_SIZE);
+    EXPECT_STREQ(buf, dummyDataStr.c_str());
+}
+
+} // namespace android