Wire uploading to MtpDocumentsProvider.

This patch does not yet allow to upload files, but uploading (creating)
directories already works.

Bug: 22545670
Change-Id: If4d5a53aa26f791475bb1a783e0ac9540d6760c1
diff --git a/api/current.txt b/api/current.txt
index 3e1f5a5..9f02047 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18136,7 +18136,7 @@
     method public final int getThumbPixWidth();
   }
 
-  public class MtpObjectInfo.Builder {
+  public static class MtpObjectInfo.Builder {
     ctor public MtpObjectInfo.Builder();
     ctor public MtpObjectInfo.Builder(android.mtp.MtpObjectInfo);
     method public android.mtp.MtpObjectInfo build();
diff --git a/api/system-current.txt b/api/system-current.txt
index 984d395..7c4c81e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19649,7 +19649,7 @@
     method public final int getThumbPixWidth();
   }
 
-  public class MtpObjectInfo.Builder {
+  public static class MtpObjectInfo.Builder {
     ctor public MtpObjectInfo.Builder();
     ctor public MtpObjectInfo.Builder(android.mtp.MtpObjectInfo);
     method public android.mtp.MtpObjectInfo build();
diff --git a/media/java/android/mtp/MtpObjectInfo.java b/media/java/android/mtp/MtpObjectInfo.java
index d2824b5..f79d52e 100644
--- a/media/java/android/mtp/MtpObjectInfo.java
+++ b/media/java/android/mtp/MtpObjectInfo.java
@@ -256,7 +256,7 @@
     /**
      * Builds a new object info instance.
      */
-    public class Builder {
+    public static class Builder {
         private MtpObjectInfo mObjectInfo;
 
         public Builder() {
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index ad804f3..9dd3861 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -430,12 +430,14 @@
 android_mtp_MtpDevice_send_object_info(JNIEnv *env, jobject thiz, jobject info)
 {
     MtpDevice* device = get_device_from_object(env, thiz);
-    if (!device)
+    if (!device) {
         return JNI_FALSE;
+    }
 
     // Updating existing objects is not supported.
-    if (env->GetIntField(info, field_objectInfo_handle) != -1)
+    if (env->GetIntField(info, field_objectInfo_handle) != -1) {
         return JNI_FALSE;
+    }
 
     MtpObjectInfo* object_info = new MtpObjectInfo(-1);
     object_info->mStorageID = env->GetIntField(info, field_objectInfo_storageId);
@@ -456,17 +458,21 @@
     object_info->mSequenceNumber = env->GetIntField(info, field_objectInfo_sequenceNumber);
 
     jstring name_jstring = (jstring) env->GetObjectField(info, field_objectInfo_name);
-    const char* name_string = env->GetStringUTFChars(name_jstring, NULL);
-    object_info->mName = strdup(name_string);
-    env->ReleaseStringUTFChars(name_jstring, name_string);
+    if (name_jstring != NULL) {
+        const char* name_string = env->GetStringUTFChars(name_jstring, NULL);
+        object_info->mName = strdup(name_string);
+        env->ReleaseStringUTFChars(name_jstring, name_string);
+    }
 
     object_info->mDateCreated = env->GetLongField(info, field_objectInfo_dateCreated) / 1000LL;
     object_info->mDateModified = env->GetLongField(info, field_objectInfo_dateModified) / 1000LL;
 
     jstring keywords_jstring = (jstring) env->GetObjectField(info, field_objectInfo_keywords);
-    const char* keywords_string = env->GetStringUTFChars(keywords_jstring, NULL);
-    object_info->mKeywords = strdup(keywords_string);
-    env->ReleaseStringUTFChars(keywords_jstring, keywords_string);
+    if (keywords_jstring != NULL) {
+        const char* keywords_string = env->GetStringUTFChars(keywords_jstring, NULL);
+        object_info->mKeywords = strdup(keywords_string);
+        env->ReleaseStringUTFChars(keywords_jstring, keywords_string);
+    }
 
     int object_handle = device->sendObjectInfo(object_info);
     if (object_handle == -1) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java
index 98775b3..7126694 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java
@@ -32,6 +32,7 @@
     private final Date mDateModified;
     private final int mSize;
     private final int mThumbSize;
+    private final boolean mReadOnly;
 
     /**
      * Constructor for root document.
@@ -40,9 +41,10 @@
         this(DUMMY_HANDLE_FOR_ROOT,
              0x3001,  // Directory.
              root.mDescription,
-             null,  // Unknown,
+             null,    // Unknown name.
              (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE),
-             0);
+             0,       // Total size.
+             true);   // Writable.
     }
 
     MtpDocument(MtpObjectInfo objectInfo) {
@@ -51,7 +53,8 @@
              objectInfo.getName(),
              objectInfo.getDateModified() != 0 ? new Date(objectInfo.getDateModified()) : null,
              objectInfo.getCompressedSize(),
-             objectInfo.getThumbCompressedSize());
+             objectInfo.getThumbCompressedSize(),
+             objectInfo.getProtectionStatus() != 0);
     }
 
     MtpDocument(int objectHandle,
@@ -59,13 +62,15 @@
                 String name,
                 Date dateModified,
                 int size,
-                int thumbSize) {
+                int thumbSize,
+                boolean readOnly) {
         this.mObjectHandle = objectHandle;
         this.mFormat = format;
         this.mName = name;
         this.mDateModified = dateModified;
         this.mSize = size;
         this.mThumbSize = thumbSize;
+        this.mReadOnly = readOnly;
     }
 
     void addToCursor(Identifier rootIdentifier, MatrixCursor.RowBuilder builder) {
@@ -82,7 +87,7 @@
 
         builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
         builder.add(Document.COLUMN_DISPLAY_NAME, mName);
-        builder.add(Document.COLUMN_MIME_TYPE, getMimeType());
+        builder.add(Document.COLUMN_MIME_TYPE, formatTypeToMimeType(mFormat));
         builder.add(
                 Document.COLUMN_LAST_MODIFIED,
                 mDateModified != null ? mDateModified.getTime() : null);
@@ -90,9 +95,9 @@
         builder.add(Document.COLUMN_SIZE, mSize);
     }
 
-    private String getMimeType() {
+    static String formatTypeToMimeType(int format) {
         // TODO: Add complete list of mime types.
-        switch (mFormat) {
+        switch (format) {
             case 0x3001:
                 return DocumentsContract.Document.MIME_TYPE_DIR;
             case 0x3009:
@@ -100,7 +105,21 @@
             case 0x3801:
                 return "image/jpeg";
             default:
-                return "";
+                return "application/octet-stream";
+        }
+    }
+
+    static int mimeTypeToFormatType(String mimeType) {
+        // TODO: Add complete list of mime types.
+        switch (mimeType.toLowerCase()) {
+            case Document.MIME_TYPE_DIR:
+                return 0x3001;
+            case "audio/mp3":
+                return 0x3009;
+            case "image/jpeg":
+                return 0x3801;
+            default:
+                return 0x3000;  // Undefined object.
         }
     }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 3a98a58..7536415 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -96,7 +96,7 @@
             final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId);
             final MatrixCursor.RowBuilder builder = cursor.newRow();
             builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId());
-            builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD);
+            builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
             builder.add(Root.COLUMN_TITLE, root.mDescription);
             builder.add(
                     Root.COLUMN_DOCUMENT_ID,
@@ -214,6 +214,24 @@
         mDocumentLoader.clearCache();
     }
 
+    @Override
+    public String createDocument(String parentDocumentId, String mimeType, String displayName)
+            throws FileNotFoundException {
+        try {
+            final Identifier parentId = Identifier.createFromDocumentId(parentDocumentId);
+            final int objectHandle = mMtpManager.createDocument(
+                    parentId.mDeviceId, parentId.mStorageId, parentId.mObjectHandle, mimeType,
+                    displayName);
+            final String documentId =  new Identifier(parentId.mDeviceId, parentId.mStorageId,
+                   objectHandle).toDocumentId();
+            notifyChildDocumentsChange(parentDocumentId);
+            return documentId;
+        } catch (IOException error) {
+            Log.e(TAG, error.getMessage());
+            throw new FileNotFoundException(error.getMessage());
+        }
+    }
+
     void openDevice(int deviceId) throws IOException {
         mMtpManager.openDevice(deviceId);
         mRootScanner.scanNow();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
index 3afc173..27ba794 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java
@@ -21,7 +21,10 @@
 import android.hardware.usb.UsbDeviceConnection;
 import android.hardware.usb.UsbManager;
 import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
 import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
 import android.util.SparseArray;
 
 import java.io.FileNotFoundException;
@@ -134,6 +137,22 @@
         }
     }
 
+    synchronized int createDocument(int deviceId, int storageId, int parentObjectHandle,
+            String mimeType, String name) throws IOException {
+        final MtpDevice device = getDevice(deviceId);
+        final MtpObjectInfo objectInfo = new MtpObjectInfo.Builder()
+                .setName(name)
+                .setStorageId(storageId)
+                .setParent(parentObjectHandle)
+                .setFormat(MtpDocument.mimeTypeToFormatType(mimeType))
+                .build();
+        final MtpObjectInfo result = device.sendObjectInfo(objectInfo);
+        if (result == null) {
+            throw new IOException("Failed to create a document");
+        }
+        return result.getObjectHandle();
+    }
+
     synchronized int getParent(int deviceId, int objectHandle) throws IOException {
         final MtpDevice device = getDevice(deviceId);
         final int result = (int) device.getParent(objectHandle);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 5504147..1e015bd 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -91,7 +91,8 @@
                     "file" + objectHandle,
                     new Date(),
                     1024,
-                    0 /* thumbnail size */));
+                    0 /* thumbnail size */,
+                    false /* not read only */));
         }
         manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
     }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index c1da59f..f06e2ff 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -210,7 +210,8 @@
                 "image.jpg" /* display name */,
                 new Date(1422716400000L) /* modified date */,
                 1024 * 1024 * 5 /* file size */,
-                1024 * 50 /* thumbnail size */));
+                1024 * 50 /* thumbnail size */,
+                true /* read only */));
         final Cursor cursor = mProvider.queryDocument("0_1_2", null);
         assertEquals(1, cursor.getCount());
 
@@ -257,7 +258,8 @@
                 "image.jpg" /* display name */,
                 new Date(0) /* modified date */,
                 1024 * 1024 * 5 /* file size */,
-                1024 * 50 /* thumbnail size */));
+                1024 * 50 /* thumbnail size */,
+                true /* read only */));
 
         final Cursor cursor = mProvider.queryChildDocuments("0_0_0", null, null);
         assertEquals(1, cursor.getCount());
@@ -302,7 +304,8 @@
                 "image.jpg" /* display name */,
                 new Date(1422716400000L) /* modified date */,
                 1024 * 1024 * 5 /* file size */,
-                1024 * 50 /* thumbnail size */));
+                1024 * 50 /* thumbnail size */,
+                false /* not read only */));
         mMtpManager.setParent(0, 1, 2);
         mProvider.deleteDocument("0_0_1");
         assertEquals(1, mResolver.getChangeCount(