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, ×tampUs));
- 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