Start using MtpDatabase in DocumentLoader.

BUG=25704514

Change-Id: I4d9247c148679ee7e40a1a03443e4c0299b1e44d
diff --git a/src/com/android/mtp/DocumentLoader.java b/src/com/android/mtp/DocumentLoader.java
index 0d4265a..1c96906 100644
--- a/src/com/android/mtp/DocumentLoader.java
+++ b/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,7 @@
 
 import android.content.ContentResolver;
 import android.database.Cursor;
-import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteException;
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -26,6 +26,7 @@
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Date;
@@ -44,12 +45,14 @@
 
     private final MtpManager mMtpManager;
     private final ContentResolver mResolver;
+    private final MtpDatabase mDatabase;
     private final TaskList mTaskList = new TaskList();
     private boolean mHasBackgroundThread = false;
 
-    DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
+    DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
         mMtpManager = mtpManager;
         mResolver = resolver;
+        mDatabase = database;
     }
 
     private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
@@ -65,13 +68,17 @@
             throws IOException {
         LoaderTask task = mTaskList.findTask(parent);
         if (task == null) {
+            if (parent.mDocumentId == null) {
+                throw new FileNotFoundException("Parent not found.");
+            }
+
             int parentHandle = parent.mObjectHandle;
             // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
             // getObjectHandles if we would like to obtain children under the root.
             if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
                 parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
             }
-            task = new LoaderTask(parent, mMtpManager.getObjectHandles(
+            task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
                     parent.mDeviceId, parent.mStorageId, parentHandle));
             task.fillDocuments(loadDocuments(
                     mMtpManager,
@@ -83,11 +90,10 @@
         }
 
         mTaskList.addFirst(task);
-        if (!task.completed() && !mHasBackgroundThread) {
+        if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
             mHasBackgroundThread = true;
             new BackgroundLoaderThread().start();
         }
-
         return task.createCursor(mResolver, columnNames);
     }
 
@@ -120,26 +126,20 @@
                     deviceId = task.mIdentifier.mDeviceId;
                     handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
                 }
-                MtpObjectInfo[] objectInfos;
+
                 try {
-                    objectInfos = loadDocuments(mMtpManager, deviceId, handles);
-                } catch (IOException exception) {
-                    objectInfos = null;
-                    Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
-                }
-                synchronized (DocumentLoader.this) {
-                    if (objectInfos != null) {
-                        task.fillDocuments(objectInfos);
-                        final boolean shouldNotify =
-                                task.mLastNotified.getTime() <
-                                new Date().getTime() - NOTIFY_PERIOD_MS ||
-                                task.completed();
-                        if (shouldNotify) {
-                            task.notify(mResolver);
-                        }
-                    } else {
-                        mTaskList.remove(task);
+                    final MtpObjectInfo[] objectInfos =
+                            loadDocuments(mMtpManager, deviceId, handles);
+                    task.fillDocuments(objectInfos);
+                    final boolean shouldNotify =
+                            task.mLastNotified.getTime() <
+                            new Date().getTime() - NOTIFY_PERIOD_MS ||
+                            task.getState() != LoaderTask.STATE_LOADING;
+                    if (shouldNotify) {
+                        task.notify(mResolver);
                     }
+                } catch (IOException exception) {
+                    task.setError(exception);
                 }
             }
         }
@@ -156,7 +156,7 @@
 
         LoaderTask findRunningTask() {
             for (int i = 0; i < size(); i++) {
-                if (!get(i).completed())
+                if (get(i).getState() == LoaderTask.STATE_LOADING)
                     return get(i);
             }
             return null;
@@ -165,7 +165,7 @@
         void clearCompletedTasks() {
             int i = 0;
             while (i < size()) {
-                if (get(i).completed()) {
+                if (get(i).getState() == LoaderTask.STATE_COMPLETED) {
                     remove(i);
                 } else {
                     i++;
@@ -186,36 +186,51 @@
     }
 
     private static class LoaderTask {
+        static final int STATE_LOADING = 0;
+        static final int STATE_COMPLETED = 1;
+        static final int STATE_ERROR = 2;
+
+        final MtpDatabase mDatabase;
         final Identifier mIdentifier;
         final int[] mObjectHandles;
-        final MtpObjectInfo[] mObjectInfos;
         Date mLastNotified;
         int mNumLoaded;
+        Exception mError;
 
-        LoaderTask(Identifier identifier, int[] objectHandles) {
+        LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) {
+            mDatabase = database;
             mIdentifier = identifier;
             mObjectHandles = objectHandles;
-            mObjectInfos = new MtpObjectInfo[mObjectHandles.length];
             mNumLoaded = 0;
             mLastNotified = new Date();
         }
 
-        Cursor createCursor(ContentResolver resolver, String[] columnNames) {
-            final MatrixCursor cursor = new MatrixCursor(columnNames);
-            final Identifier rootIdentifier = new Identifier(
-                    mIdentifier.mDeviceId, mIdentifier.mStorageId);
-            for (int i = 0; i < mNumLoaded; i++) {
-                CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow());
-            }
+        Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
             final Bundle extras = new Bundle();
-            extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
+            switch (getState()) {
+                case STATE_LOADING:
+                    extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+                    break;
+                case STATE_ERROR:
+                    throw new IOException(mError);
+            }
+
+            final Cursor cursor = mDatabase.queryChildDocuments(
+                    columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
             cursor.setNotificationUri(resolver, createUri());
             cursor.respond(extras);
+
             return cursor;
         }
 
-        boolean completed() {
-            return mNumLoaded == mObjectInfos.length;
+        int getState() {
+            if (mError != null) {
+                return STATE_ERROR;
+            } else if (mNumLoaded == mObjectHandles.length) {
+                return STATE_COMPLETED;
+            } else {
+                return STATE_LOADING;
+            }
         }
 
         int[] getUnloadedObjectHandles(int count) {
@@ -230,9 +245,32 @@
             mLastNotified = new Date();
         }
 
-        void fillDocuments(MtpObjectInfo[] objectInfos) {
-            for (int i = 0; i < objectInfos.length; i++) {
-                mObjectInfos[mNumLoaded++] = objectInfos[i];
+        void fillDocuments(MtpObjectInfo[] objectInfoList) {
+            if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
+                return;
+            }
+            if (mNumLoaded == 0) {
+                mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+            try {
+                mDatabase.putChildDocuments(
+                        mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
+                mNumLoaded += objectInfoList.length;
+            } catch (SQLiteException exp) {
+                mError = exp;
+                mNumLoaded = 0;
+            }
+            if (getState() != STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+            }
+        }
+
+        void setError(Exception message) {
+            final int lastState = getState();
+            mError = message;
+            mNumLoaded = 0;
+            if (lastState == STATE_LOADING) {
+                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
             }
         }
 
diff --git a/src/com/android/mtp/Identifier.java b/src/com/android/mtp/Identifier.java
index ae29f52..4238721 100644
--- a/src/com/android/mtp/Identifier.java
+++ b/src/com/android/mtp/Identifier.java
@@ -23,6 +23,7 @@
     final int mDeviceId;
     final int mStorageId;
     final int mObjectHandle;
+    final String mDocumentId;
 
     static Identifier createFromRootId(String rootId) {
         final String[] components = rootId.split("_");
@@ -45,9 +46,14 @@
     }
 
     Identifier(int deviceId, int storageId, int objectHandle) {
+        this(deviceId, storageId, objectHandle, null);
+    }
+
+    Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
         mDeviceId = deviceId;
         mStorageId = storageId;
         mObjectHandle = objectHandle;
+        mDocumentId = documentId;
     }
 
     // TODO: Make the ID persistent.
diff --git a/src/com/android/mtp/MtpDatabase.java b/src/com/android/mtp/MtpDatabase.java
index 874eeb3..3dc69cc 100644
--- a/src/com/android/mtp/MtpDatabase.java
+++ b/src/com/android/mtp/MtpDatabase.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 
@@ -79,8 +80,8 @@
     private final Map<String, Integer> mMappingMode = new HashMap<>();
 
     @VisibleForTesting
-    MtpDatabase(Context context, boolean inMemory) {
-        mDatabase = new MtpDatabaseInternal(context, inMemory);
+    MtpDatabase(Context context, int flags) {
+        mDatabase = new MtpDatabaseInternal(context, flags);
     }
 
     /**
@@ -111,7 +112,29 @@
      */
     @VisibleForTesting
     Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
-        return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
+        return queryChildDocuments(columnNames, parentDocumentId, false);
+    }
+
+    @VisibleForTesting
+    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
+        final String[] newColumnNames = new String[columnNames.length];
+
+        // TODO: Temporary replace document ID with old format.
+        for (int i = 0; i < columnNames.length; i++) {
+            if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
+                newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
+                        " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
+                        DocumentsContract.Document.COLUMN_DOCUMENT_ID;
+            } else {
+                newColumnNames[i] = columnNames[i];
+            }
+        }
+
+        return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
+    }
+
+    Identifier createIdentifier(String parentDocumentId) {
+        return mDatabase.createIdentifier(parentDocumentId);
     }
 
     /**
@@ -193,9 +216,13 @@
             int i = 0;
             for (final MtpRoot root : roots) {
                 // Use the same value for the root ID and the corresponding document ID.
-                values.put(
-                        Root.COLUMN_ROOT_ID,
-                        valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
+                final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+                // If it fails to insert/update documents, the document ID will be set with -1.
+                // In this case we don't insert/update root extra information neither.
+                if (documentId == null) {
+                    continue;
+                }
+                values.put(Root.COLUMN_ROOT_ID, documentId);
                 values.put(
                         Root.COLUMN_FLAGS,
                         Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
diff --git a/src/com/android/mtp/MtpDatabaseConstants.java b/src/com/android/mtp/MtpDatabaseConstants.java
index 977b12e..97c1d29 100644
--- a/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/src/com/android/mtp/MtpDatabaseConstants.java
@@ -26,6 +26,9 @@
     static final int DATABASE_VERSION = 1;
     static final String DATABASE_NAME = null;
 
+    static final int FLAG_DATABASE_IN_MEMORY = 1;
+    static final int FLAG_DATABASE_IN_FILE = 0;
+
     /**
      * Table representing documents including root documents.
      */
diff --git a/src/com/android/mtp/MtpDatabaseInternal.java b/src/com/android/mtp/MtpDatabaseInternal.java
index df2875e..9c5d6b6 100644
--- a/src/com/android/mtp/MtpDatabaseInternal.java
+++ b/src/com/android/mtp/MtpDatabaseInternal.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.provider.DocumentsContract.Document;
@@ -35,8 +36,11 @@
  */
 class MtpDatabaseInternal {
     private static class OpenHelper extends SQLiteOpenHelper {
-        public OpenHelper(Context context, boolean inMemory) {
-            super(context, inMemory ? null : DATABASE_NAME, null, DATABASE_VERSION);
+        public OpenHelper(Context context, int flags) {
+            super(context,
+                  flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+                  null,
+                  DATABASE_VERSION);
         }
 
         @Override
@@ -54,8 +58,8 @@
 
     private final SQLiteDatabase mDatabase;
 
-    MtpDatabaseInternal(Context context, boolean inMemory) {
-        final OpenHelper helper = new OpenHelper(context, inMemory);
+    MtpDatabaseInternal(Context context, int flags) {
+        final OpenHelper helper = new OpenHelper(context, flags);
         mDatabase = helper.getWritableDatabase();
     }
 
@@ -122,6 +126,64 @@
     }
 
     /**
+     * Gets identifier from document ID.
+     * @param documentId Document ID.
+     * @return Identifier.
+     */
+    Identifier createIdentifier(String documentId) {
+        // Currently documentId is old format.
+        final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
+        final String selection;
+        final String[] args;
+        if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId);
+        } else {
+            selection = COLUMN_DEVICE_ID + "= ? AND " +
+                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
+                    COLUMN_STORAGE_ID + "= ? AND " +
+                    COLUMN_OBJECT_HANDLE + " = ?";
+            args = strings(
+                    oldIdentifier.mDeviceId,
+                    ROW_STATE_VALID,
+                    ROW_STATE_INVALIDATED,
+                    oldIdentifier.mStorageId,
+                    oldIdentifier.mObjectHandle);
+        }
+
+        final Cursor cursor = mDatabase.query(
+                TABLE_DOCUMENTS,
+                strings(Document.COLUMN_DOCUMENT_ID),
+                selection,
+                args,
+                null,
+                null,
+                null,
+                "1");
+        try {
+            if (cursor.getCount() == 0) {
+                return oldIdentifier;
+            } else {
+                cursor.moveToNext();
+                return new Identifier(
+                        oldIdentifier.mDeviceId,
+                        oldIdentifier.mStorageId,
+                        oldIdentifier.mObjectHandle,
+                        cursor.getString(0));
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
      * Starts adding new documents.
      * The methods decides mapping mode depends on if all documents under the given parent have MTP
      * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
@@ -164,7 +226,7 @@
      * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
      * rows. If the methods adds rows to database, it updates valueList with correct document ID.
      *
-     * @param valuesList Values that are stored in the database.
+     * @param valuesList Values for documents to be stored in the database.
      * @param selection SQL where closure to select rows that shares the same parent.
      * @param arg Argument for selection SQL.
      * @param heuristic Whether the mapping mode is heuristic.
@@ -191,23 +253,32 @@
                         null,
                         null,
                         "1");
-                final long rowId;
-                if (candidateCursor.getCount() == 0) {
-                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
-                    added = true;
-                } else if (!heuristic) {
-                    candidateCursor.moveToNext();
-                    final String documentId = candidateCursor.getString(0);
-                    rowId = mDatabase.update(
-                            TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
-                } else {
-                    values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
-                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                try {
+                    final long rowId;
+                    if (candidateCursor.getCount() == 0) {
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                        if (rowId == -1) {
+                            throw new SQLiteException("Failed to put a document into database.");
+                        }
+                        added = true;
+                    } else if (!heuristic) {
+                        candidateCursor.moveToNext();
+                        final String documentId = candidateCursor.getString(0);
+                        rowId = mDatabase.update(
+                                TABLE_DOCUMENTS,
+                                values,
+                                SELECTION_DOCUMENT_ID,
+                                strings(documentId));
+                    } else {
+                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+                    }
+                    // Document ID is a primary integer key of the table. So the returned row
+                    // IDs should be same with the document ID.
+                    values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+                } finally {
+                    candidateCursor.close();
                 }
-                // Document ID is a primary integer key of the table. So the returned row
-                // IDs should be same with the document ID.
-                values.put(Document.COLUMN_DOCUMENT_ID, rowId);
-                candidateCursor.close();
             }
 
             mDatabase.setTransactionSuccessful();
diff --git a/src/com/android/mtp/MtpDocumentsProvider.java b/src/com/android/mtp/MtpDocumentsProvider.java
index f7e9463..f0f8161 100644
--- a/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/src/com/android/mtp/MtpDocumentsProvider.java
@@ -78,7 +78,7 @@
         mMtpManager = new MtpManager(getContext());
         mResolver = getContext().getContentResolver();
         mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
-        mDatabase = new MtpDatabase(getContext(), false);
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
         mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
         return true;
     }
@@ -155,7 +155,7 @@
         if (projection == null) {
             projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
         }
-        final Identifier parentIdentifier = Identifier.createFromDocumentId(parentDocumentId);
+        final Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId);
         try {
             return getDocumentLoader(parentIdentifier).queryChildDocuments(
                     projection, parentIdentifier);
@@ -255,7 +255,7 @@
 
     void openDevice(int deviceId) throws IOException {
         mMtpManager.openDevice(deviceId);
-        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver));
+        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
         mRootScanner.scanNow();
     }
 
@@ -318,9 +318,9 @@
         public final PipeManager mPipeManager;
         public final DocumentLoader mDocumentLoader;
 
-        public DeviceToolkit(MtpManager manager, ContentResolver resolver) {
+        public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
             mPipeManager = new PipeManager();
-            mDocumentLoader = new DocumentLoader(manager, resolver);
+            mDocumentLoader = new DocumentLoader(manager, resolver, database);
         }
     }
 }
diff --git a/src/com/android/mtp/RootScanner.java b/src/com/android/mtp/RootScanner.java
index 415f89e..d9ed4ab 100644
--- a/src/com/android/mtp/RootScanner.java
+++ b/src/com/android/mtp/RootScanner.java
@@ -2,6 +2,7 @@
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
+import android.database.sqlite.SQLiteException;
 import android.net.Uri;
 import android.os.Process;
 import android.provider.DocumentsContract;
@@ -93,16 +94,17 @@
                     }
                     boolean changed = false;
                     for (int deviceId : deviceIds) {
+                        mDatabase.startAddingRootDocuments(deviceId);
                         try {
-                            mDatabase.startAddingRootDocuments(deviceId);
                             changed = mDatabase.putRootDocuments(
                                     deviceId, mResources, mManager.getRoots(deviceId)) || changed;
-                            changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
-                        } catch (IOException exp) {
+                        } catch (IOException|SQLiteException exp) {
                             // The error may happen on the device. We would like to continue getting
                             // roots for other devices.
                             Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
                             continue;
+                        } finally {
+                            changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
                         }
                     }
                     if (changed) {
diff --git a/tests/src/com/android/mtp/DocumentLoaderTest.java b/tests/src/com/android/mtp/DocumentLoaderTest.java
index f6d6d44..a80eb51 100644
--- a/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -31,16 +31,28 @@
 
 @MediumTest
 public class DocumentLoaderTest extends AndroidTestCase {
+    private MtpDatabase mDatabase;
     private BlockableTestMtpManager mManager;
     private TestContentResolver mResolver;
     private DocumentLoader mLoader;
-    final private Identifier mParentIdentifier = new Identifier(0, 0, 0);
+    final private Identifier mParentIdentifier = new Identifier(0, 0, 0, "1");
 
     @Override
     public void setUp() {
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
         mManager = new BlockableTestMtpManager(getContext());
         mResolver = new TestContentResolver();
-        mLoader = new DocumentLoader(mManager, mResolver);
+        mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
+    }
+
+    @Override
+    public void tearDown() {
+        mDatabase.close();
     }
 
     public void testBasic() throws Exception {
@@ -87,6 +99,7 @@
             childDocuments[i] = objectHandle;
             manager.setObjectInfo(0, new MtpObjectInfo.Builder()
                     .setObjectHandle(objectHandle)
+                    .setName(Integer.toString(i))
                     .build());
         }
         manager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, childDocuments);
diff --git a/tests/src/com/android/mtp/MtpDatabaseTest.java b/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7641e3a..25dd1c8 100644
--- a/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -45,7 +45,7 @@
 
     @Override
     public void setUp() {
-        mDatabase = new MtpDatabase(getContext(), true);
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
     }
 
     @Override
diff --git a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 70923c0..82e08cd 100644
--- a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -43,7 +43,7 @@
         mResolver = new TestContentResolver();
         mMtpManager = new TestMtpManager(getContext());
         mProvider = new MtpDocumentsProvider();
-        mDatabase = new MtpDatabase(getContext(), true);
+        mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
         mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
     }
 
@@ -200,6 +200,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
                 .setName("image.jpg")
                 .setDateModified(1422716400000L)
@@ -210,6 +211,7 @@
         assertEquals(1, cursor.getCount());
 
         cursor.moveToNext();
+
         assertEquals("0_1_2", cursor.getString(0));
         assertEquals("image/jpeg", cursor.getString(1));
         assertEquals("image.jpg", cursor.getString(2));
@@ -227,6 +229,7 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(2)
+                .setStorageId(1)
                 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
                 .setName("directory")
                 .setDateModified(1422716400000L)
@@ -277,6 +280,12 @@
         mProvider.openDevice(0);
         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
 
+        mDatabase.startAddingRootDocuments(0);
+        mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
+                new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
+        });
+        mDatabase.stopAddingRootDocuments(0);
+
         mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
                 .setObjectHandle(1)
                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
diff --git a/tests/src/com/android/mtp/TestMtpManager.java b/tests/src/com/android/mtp/TestMtpManager.java
index 3d92cc2..3833799 100644
--- a/tests/src/com/android/mtp/TestMtpManager.java
+++ b/tests/src/com/android/mtp/TestMtpManager.java
@@ -96,7 +96,7 @@
         if (mRoots.containsKey(deviceId)) {
             return mRoots.get(deviceId);
         } else {
-            throw new IOException("getRoots error");
+            throw new IOException("getRoots error: " + Integer.toString(deviceId));
         }
     }