MTP: Implement GetThumb command

This allows the PC to access thumbnails in JPEG files over MTP/PTP

Bug: 3219495

Change-Id: I4964f8b4826dffb7f0f77464ec91bd2e97a2f007
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 4fd4147..77cedd5 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -27,9 +27,11 @@
     libcamera_client \
     libsqlite \
     libmtp \
-    libusbhost
+    libusbhost \
+    libexif
 
 LOCAL_C_INCLUDES += \
+    external/jhead \
     external/tremor/Tremor \
     frameworks/base/core/jni \
     frameworks/base/media/libmedia \
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 585cd30..0f3c063 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -35,6 +35,10 @@
 #include "MtpUtils.h"
 #include "mtp.h"
 
+extern "C" {
+#include "jhead.h"
+}
+
 using namespace android;
 
 // ----------------------------------------------------------------------------
@@ -141,6 +145,8 @@
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
                                             MtpObjectInfo& info);
 
+    virtual void*                   getThumbnail(MtpObjectHandle handle, size_t& outThumbSize);
+
     virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
                                             int64_t& outFileLength,
@@ -776,10 +782,67 @@
     info.mName = strdup((const char *)temp);
     env->ReleaseCharArrayElements(mStringBuffer, str, 0);
 
+    // read EXIF data for thumbnail information
+    if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) {
+        MtpString path;
+        int64_t length;
+        MtpObjectFormat format;
+        if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) {
+            ResetJpgfile();
+             // Start with an empty image information structure.
+            memset(&ImageInfo, 0, sizeof(ImageInfo));
+            ImageInfo.FlashUsed = -1;
+            ImageInfo.MeteringMode = -1;
+            ImageInfo.Whitebalance = -1;
+            strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+            if (ReadJpegFile((const char*)path, READ_METADATA)) {
+                Section_t* section = FindSection(M_EXIF);
+                if (section) {
+                    info.mThumbCompressedSize = ImageInfo.ThumbnailSize;
+                    info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+                    info.mImagePixWidth = ImageInfo.Width;
+                    info.mImagePixHeight = ImageInfo.Height;
+                }
+            }
+            DiscardData();
+        }
+    }
+
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return MTP_RESPONSE_OK;
 }
 
+void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) {
+    MtpString path;
+    int64_t length;
+    MtpObjectFormat format;
+    void* result = NULL;
+    outThumbSize = 0;
+
+    if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK
+            && (format == MTP_FORMAT_EXIF_JPEG || format == MTP_FORMAT_JFIF)) {
+        ResetJpgfile();
+         // Start with an empty image information structure.
+        memset(&ImageInfo, 0, sizeof(ImageInfo));
+        ImageInfo.FlashUsed = -1;
+        ImageInfo.MeteringMode = -1;
+        ImageInfo.Whitebalance = -1;
+        strncpy(ImageInfo.FileName, (const char *)path, PATH_MAX);
+        if (ReadJpegFile((const char*)path, READ_METADATA)) {
+            Section_t* section = FindSection(M_EXIF);
+            if (section) {
+                outThumbSize = ImageInfo.ThumbnailSize;
+                result = malloc(outThumbSize);
+                if (result)
+                    memcpy(result, section->Data + ImageInfo.ThumbnailOffset + 8, outThumbSize);
+            }
+            DiscardData();
+        }
+    }
+
+    return result;
+}
+
 MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
                                             int64_t& outFileLength,
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index 0b0c80d..817eac05 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -388,6 +388,16 @@
     int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
     return (ret < 0 ? ret : 0);
 }
+
+int MtpDataPacket::writeData(int fd, void* data, uint32_t length) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length + MTP_CONTAINER_HEADER_SIZE);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+    int ret = ::write(fd, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    if (ret == MTP_CONTAINER_HEADER_SIZE)
+        ret = ::write(fd, data, length);
+    return (ret < 0 ? ret : 0);
+}
+
 #endif // MTP_DEVICE
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index 577cea1..8a08948 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -100,6 +100,7 @@
     // write our data to the given file descriptor
     int                 write(int fd);
     int                 writeDataHeader(int fd, uint32_t length);
+    int                 writeData(int fd, void* data, uint32_t length);
 #endif
 
 #ifdef MTP_HOST
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index d7bde00..4e6ac7a 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -84,6 +84,8 @@
     virtual MtpResponseCode         getObjectInfo(MtpObjectHandle handle,
                                             MtpObjectInfo& info) = 0;
 
+    virtual void*                   getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) = 0;
+
     virtual MtpResponseCode         getObjectFilePath(MtpObjectHandle handle,
                                             MtpString& outFilePath,
                                             int64_t& outFileLength,
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index b744b5b..4a8fd3e 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -50,7 +50,7 @@
     MTP_OPERATION_GET_OBJECT_HANDLES,
     MTP_OPERATION_GET_OBJECT_INFO,
     MTP_OPERATION_GET_OBJECT,
-//    MTP_OPERATION_GET_THUMB,
+    MTP_OPERATION_GET_THUMB,
     MTP_OPERATION_DELETE_OBJECT,
     MTP_OPERATION_SEND_OBJECT_INFO,
     MTP_OPERATION_SEND_OBJECT,
@@ -370,6 +370,9 @@
         case MTP_OPERATION_GET_OBJECT:
             response = doGetObject();
             break;
+        case MTP_OPERATION_GET_THUMB:
+            response = doGetThumb();
+            break;
         case MTP_OPERATION_GET_PARTIAL_OBJECT:
         case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
             response = doGetPartialObject(operation);
@@ -736,6 +739,22 @@
     return MTP_RESPONSE_OK;
 }
 
+MtpResponseCode MtpServer::doGetThumb() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    size_t thumbSize;
+    void* thumb = mDatabase->getThumbnail(handle, thumbSize);
+    if (thumb) {
+        // send data
+        mData.setOperationCode(mRequest.getOperationCode());
+        mData.setTransactionID(mRequest.getTransactionID());
+        mData.writeData(mFD, thumb, thumbSize);
+        free(thumb);
+        return MTP_RESPONSE_OK;
+    } else {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+}
+
 MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
     if (!hasStorage())
         return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index b06eb28..859a18e 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -133,6 +133,7 @@
     MtpResponseCode     doGetObjectPropList();
     MtpResponseCode     doGetObjectInfo();
     MtpResponseCode     doGetObject();
+    MtpResponseCode     doGetThumb();
     MtpResponseCode     doGetPartialObject(MtpOperationCode operation);
     MtpResponseCode     doSendObjectInfo();
     MtpResponseCode     doSendObject();