Fix doc flag for device having multiple storages

Previously MtpDocumentsProvider#queryDocument returned
FLAG_DIR_SUPPORTS_CREATE for device having multiple storages.

This was wrong because if the device has multiple storages,
MtpDocumentsProvider places the storages under the device document, thus
users cannot create a document just under the device document.

Bug: 35700994
Test: MtpDocumentsProviderTests
Change-Id: Id73a34a2eaf4e10e23be3c2da7488036cea10000
(cherry picked from commit 66fcb4beae9605cd034c9437e510b21260d6f519)
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 4950af3..9b5982b9 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -200,10 +200,7 @@
                     storageCursor.close();
                 }
 
-                final RowBuilder row = result.newRow();
-                for (final String key : values.keySet()) {
-                    row.add(key, values.get(key));
-                }
+                putValuesToCursor(values, result);
             }
 
             return result;
@@ -760,7 +757,9 @@
                 Document.MIME_TYPE_DIR,
                 0,
                 MtpConstants.PROTECTION_STATUS_NONE,
-                DOCUMENT_TYPE_DEVICE));
+                // Storages are placed under device so we cannot create a document just under
+                // device.
+                DOCUMENT_TYPE_DEVICE) & ~Document.FLAG_DIR_SUPPORTS_CREATE);
         values.putNull(Document.COLUMN_SIZE);
 
         extraValues.clear();
@@ -915,6 +914,13 @@
         return results;
     }
 
+    static void putValuesToCursor(ContentValues values, MatrixCursor cursor) {
+        final RowBuilder row = cursor.newRow();
+        for (final String name : cursor.getColumnNames()) {
+            row.add(values.get(name));
+        }
+    }
+
     private static String getIdList(Set<String> ids) {
         String result = "(";
         for (final String id : ids) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index db88f2c..eb2d8aa 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -17,11 +17,13 @@
 package com.android.mtp;
 
 import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriPermission;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.database.DatabaseUtils;
 import android.database.MatrixCursor;
 import android.database.sqlite.SQLiteDiskIOException;
 import android.graphics.Point;
@@ -55,7 +57,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
-
 import libcore.io.IoUtils;
 
 /**
@@ -177,7 +178,57 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
         }
-        return mDatabase.queryDocument(documentId, projection);
+        final Cursor cursor = mDatabase.queryDocument(documentId, projection);
+        final int cursorCount = cursor.getCount();
+        if (cursorCount == 0) {
+            cursor.close();
+            throw new FileNotFoundException();
+        } else if (cursorCount != 1) {
+            cursor.close();
+            Log.wtf(TAG, "Unexpected cursor size: " + cursorCount);
+            return null;
+        }
+
+        final Identifier identifier = mDatabase.createIdentifier(documentId);
+        if (identifier.mDocumentType != MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) {
+            return cursor;
+        }
+        final String[] storageDocIds = mDatabase.getStorageDocumentIds(documentId);
+        if (storageDocIds.length != 1) {
+            return mDatabase.queryDocument(documentId, projection);
+        }
+
+        // If the documentId specifies a device having exact one storage, we repalce some device
+        // attributes with the storage attributes.
+        try {
+            final String storageName;
+            final int storageFlags;
+            try (final Cursor storageCursor = mDatabase.queryDocument(
+                    storageDocIds[0],
+                    MtpDatabase.strings(Document.COLUMN_DISPLAY_NAME, Document.COLUMN_FLAGS))) {
+                if (!storageCursor.moveToNext()) {
+                    throw new FileNotFoundException();
+                }
+                storageName = storageCursor.getString(0);
+                storageFlags = storageCursor.getInt(1);
+            }
+
+            cursor.moveToNext();
+            final ContentValues values = new ContentValues();
+            DatabaseUtils.cursorRowToContentValues(cursor, values);
+            if (values.containsKey(Document.COLUMN_DISPLAY_NAME)) {
+                values.put(Document.COLUMN_DISPLAY_NAME, mResources.getString(
+                        R.string.root_name,
+                        values.getAsString(Document.COLUMN_DISPLAY_NAME),
+                        storageName));
+            }
+            values.put(Document.COLUMN_FLAGS, storageFlags);
+            final MatrixCursor output = new MatrixCursor(projection, 1);
+            MtpDatabase.putValuesToCursor(values, output);
+            return output;
+        } finally {
+            cursor.close();
+        }
     }
 
     @Override
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index c9420d1..3fa5eb5 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -368,7 +368,7 @@
         assertEquals(0, cursor.getInt(5));
     }
 
-    public void testQueryDocument_forRoot()
+    public void testQueryDocument_forStorage()
             throws IOException, InterruptedException, TimeoutException {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] {
@@ -392,6 +392,61 @@
         assertEquals(3072, cursor.getInt(5));
     }
 
+    public void testQueryDocument_forDeviceWithSingleStorage()
+            throws IOException, InterruptedException, TimeoutException {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] {
+                new MtpRoot(
+                        0 /* deviceId */,
+                        1 /* storageId */,
+                        "Storage A" /* volume description */,
+                        1024 /* free space */,
+                        4096 /* total space */,
+                        "" /* no volume identifier */)
+        });
+        final Cursor cursor = mProvider.queryDocument("1", null);
+        assertEquals(1, cursor.getCount());
+
+        cursor.moveToNext();
+        assertEquals("1", cursor.getString(0));
+        assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
+        assertEquals("Device Storage A", cursor.getString(2));
+        assertTrue(cursor.isNull(3));
+        assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4));
+        assertTrue(cursor.isNull(5));
+    }
+
+    public void testQueryDocument_forDeviceWithTwoStorages()
+            throws IOException, InterruptedException, TimeoutException {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] {
+                new MtpRoot(
+                        0 /* deviceId */,
+                        1 /* storageId */,
+                        "Storage A" /* volume description */,
+                        1024 /* free space */,
+                        4096 /* total space */,
+                        "" /* no volume identifier */),
+                new MtpRoot(
+                        0 /* deviceId */,
+                        2 /* storageId */,
+                        "Storage B" /* volume description */,
+                        1024 /* free space */,
+                        4096 /* total space */,
+                        "" /* no volume identifier */)
+        });
+        final Cursor cursor = mProvider.queryDocument("1", null);
+        assertEquals(1, cursor.getCount());
+
+        cursor.moveToNext();
+        assertEquals("1", cursor.getString(0));
+        assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
+        assertEquals("Device", cursor.getString(2));
+        assertTrue(cursor.isNull(3));
+        assertEquals(0, cursor.getInt(4));
+        assertTrue(cursor.isNull(5));
+    }
+
     public void testQueryChildDocuments() throws Exception {
         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });