Refactor FileInserter in MediaScanner and adding unit tests for the newly added class.

Change-Id: Ia0e8c95239916fd4c21cb5bf10ac94201e6eb6f1
diff --git a/media/java/android/media/MediaInserter.java b/media/java/android/media/MediaInserter.java
new file mode 100644
index 0000000..a998407
--- /dev/null
+++ b/media/java/android/media/MediaInserter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package android.media;
+
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A MediaScanner helper class which enables us to do lazy insertion on the
+ * given provider. This class manages buffers internally and flushes when they
+ * are full. Note that you should call flushAll() after using this class.
+ * {@hide}
+ */
+public class MediaInserter {
+    private HashMap<Uri, List<ContentValues>> mRowMap =
+            new HashMap<Uri, List<ContentValues>>();
+
+    private IContentProvider mProvider;
+    private int mBufferSizePerUri;
+
+    public MediaInserter(IContentProvider provider, int bufferSizePerUri) {
+        mProvider = provider;
+        mBufferSizePerUri = bufferSizePerUri;
+    }
+
+    public void insert(Uri tableUri, ContentValues values) throws RemoteException {
+        List<ContentValues> list = mRowMap.get(tableUri);
+        if (list == null) {
+            list = new ArrayList<ContentValues>();
+            mRowMap.put(tableUri, list);
+        }
+        list.add(new ContentValues(values));
+        if (list.size() >= mBufferSizePerUri) {
+            flush(tableUri);
+        }
+    }
+
+    public void flushAll() throws RemoteException {
+        for (Uri tableUri : mRowMap.keySet()){
+            flush(tableUri);
+        }
+        mRowMap.clear();
+    }
+
+    private void flush(Uri tableUri) throws RemoteException {
+        List<ContentValues> list = mRowMap.get(tableUri);
+        if (!list.isEmpty()) {
+            ContentValues[] valuesArray = new ContentValues[list.size()];
+            valuesArray = list.toArray(valuesArray);
+            mProvider.bulkInsert(tableUri, valuesArray);
+            list.clear();
+        }
+    }
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 2d927ad..386986e 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -377,43 +377,7 @@
         }
     }
 
-    private class FileInserter {
-
-        private final Uri mUri;
-        private final ContentValues[] mValues;
-        private int mIndex;
-
-        public FileInserter(Uri uri, int count) {
-            mUri = uri;
-            mValues = new ContentValues[count];
-        }
-
-        public Uri insert(ContentValues values) {
-            if (mIndex == mValues.length) {
-                flush();
-            }
-            mValues[mIndex++] = values;
-            // URI not needed when doing bulk inserts
-            return null;
-        }
-
-        public void flush() {
-            while (mIndex < mValues.length) {
-                mValues[mIndex++] = null;
-            }
-            try {
-                mMediaProvider.bulkInsert(mUri, mValues);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in FileInserter.flush()", e);
-            }
-            mIndex = 0;
-        }
-    }
-
-    private FileInserter mAudioInserter;
-    private FileInserter mVideoInserter;
-    private FileInserter mImageInserter;
-    private FileInserter mFileInserter;
+    private MediaInserter mMediaInserter;
 
     // hashes file path to FileCacheEntry.
     // path should be lower case if mCaseInsensitivePaths is true
@@ -880,17 +844,14 @@
             }
 
             Uri tableUri = mFilesUri;
-            FileInserter inserter = mFileInserter;
+            MediaInserter inserter = mMediaInserter;
             if (!mNoMedia) {
                 if (MediaFile.isVideoFileType(mFileType)) {
                     tableUri = mVideoUri;
-                    inserter = mVideoInserter;
                 } else if (MediaFile.isImageFileType(mFileType)) {
                     tableUri = mImagesUri;
-                    inserter = mImageInserter;
                 } else if (MediaFile.isAudioFileType(mFileType)) {
                     tableUri = mAudioUri;
-                    inserter = mAudioInserter;
                 }
             }
             Uri result = null;
@@ -913,7 +874,7 @@
                 if (inserter == null || entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
                     result = mMediaProvider.insert(tableUri, values);
                 } else {
-                    result = inserter.insert(values);
+                    inserter.insert(tableUri, values);
                 }
 
                 if (result != null) {
@@ -1212,11 +1173,8 @@
             long prescan = System.currentTimeMillis();
 
             if (ENABLE_BULK_INSERTS) {
-                // create FileInserters for bulk inserts
-                mAudioInserter = new FileInserter(mAudioUri, 500);
-                mVideoInserter = new FileInserter(mVideoUri, 500);
-                mImageInserter = new FileInserter(mImagesUri, 500);
-                mFileInserter = new FileInserter(mFilesUri, 500);
+                // create MediaInserter for bulk inserts
+                mMediaInserter = new MediaInserter(mMediaProvider, 500);
             }
 
             for (int i = 0; i < directories.length; i++) {
@@ -1225,14 +1183,8 @@
 
             if (ENABLE_BULK_INSERTS) {
                 // flush remaining inserts
-                mAudioInserter.flush();
-                mVideoInserter.flush();
-                mImageInserter.flush();
-                mFileInserter.flush();
-                mAudioInserter = null;
-                mVideoInserter = null;
-                mImageInserter = null;
-                mFileInserter = null;
+                mMediaInserter.flushAll();
+                mMediaInserter = null;
             }
 
             long scan = System.currentTimeMillis();
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
index 9c45e6e..c9afa19 100644
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ b/media/tests/MediaFrameworkTest/Android.mk
@@ -7,6 +7,8 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
+LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
+
 LOCAL_PACKAGE_NAME := mediaframeworktest
 
 include $(BUILD_PACKAGE)
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index a203adc..62af3f3 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -47,6 +47,7 @@
         addMediaMetadataRetrieverStateUnitTests(suite);
         addMediaRecorderStateUnitTests(suite);
         addMediaPlayerStateUnitTests(suite);
+        addMediaScannerUnitTests(suite);
         return suite;
     }
 
@@ -89,4 +90,8 @@
         suite.addTestSuite(MediaPlayerSetVolumeStateUnitTest.class);
         suite.addTestSuite(MediaPlayerMetadataParserTest.class);
     }
+
+    private void addMediaScannerUnitTests(TestSuite suite) {
+        suite.addTestSuite(MediaInserterTest.class);
+    }
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
new file mode 100644
index 0000000..ad3c342
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+package com.android.mediaframeworktest.unit;
+
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.media.MediaInserter;
+import android.net.Uri;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import dalvik.annotation.TestTargetClass;
+
+import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
+
+@TestTargetClass(MediaInserter.class)
+public class MediaInserterTest extends InstrumentationTestCase {
+
+    private MediaInserter mMediaInserter;
+    private static final int TEST_BUFFER_SIZE = 10;
+    private IContentProvider mMockProvider;
+
+    private int mFilesCounter;
+    private int mAudioCounter;
+    private int mVideoCounter;
+    private int mImagesCounter;
+
+    private static final String sVolumeName = "external";
+    private static final Uri sAudioUri = Audio.Media.getContentUri(sVolumeName);
+    private static final Uri sVideoUri = Video.Media.getContentUri(sVolumeName);
+    private static final Uri sImagesUri = Images.Media.getContentUri(sVolumeName);
+    private static final Uri sFilesUri = Files.getContentUri(sVolumeName);
+
+    private static class MediaUriMatcher implements IArgumentMatcher {
+        private Uri mUri;
+
+        private MediaUriMatcher(Uri uri) {
+            mUri = uri;
+        }
+
+        @Override
+        public boolean matches(Object argument) {
+            if (!(argument instanceof Uri)) {
+                return false;
+            }
+
+            Uri actualUri = (Uri) argument;
+            if (actualUri == mUri) return true;
+            return false;
+        }
+
+        @Override
+        public void appendTo(StringBuffer buffer) {
+            buffer.append("expected a TableUri '").append(mUri).append("'");
+        }
+
+        private static Uri expectMediaUri(Uri in) {
+            EasyMock.reportMatcher(new MediaUriMatcher(in));
+            return null;
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mMockProvider = EasyMock.createMock(IContentProvider.class);
+        mMediaInserter = new MediaInserter(mMockProvider, TEST_BUFFER_SIZE);
+        mFilesCounter = 0;
+        mAudioCounter = 0;
+        mVideoCounter = 0;
+        mImagesCounter = 0;
+    }
+
+    private ContentValues createFileContent() {
+        ContentValues values = new ContentValues();
+        values.put("_data", "/mnt/sdcard/file" + ++mFilesCounter);
+        return values;
+    }
+
+    private ContentValues createAudioContent() {
+        ContentValues values = new ContentValues();
+        values.put("_data", "/mnt/sdcard/audio" + ++mAudioCounter);
+        return values;
+    }
+
+    private ContentValues createVideoContent() {
+        ContentValues values = new ContentValues();
+        values.put("_data", "/mnt/sdcard/video" + ++mVideoCounter);
+        return values;
+    }
+
+    private ContentValues createImageContent() {
+        ContentValues values = new ContentValues();
+        values.put("_data", "/mnt/sdcard/image" + ++mImagesCounter);
+        return values;
+    }
+
+    private ContentValues createContent(Uri uri) {
+        if (uri == sFilesUri) return createFileContent();
+        else if (uri == sAudioUri) return createAudioContent();
+        else if (uri == sVideoUri) return createVideoContent();
+        else if (uri == sImagesUri) return createImageContent();
+        else throw new IllegalArgumentException("Unknown URL: " + uri.toString());
+    }
+
+    private void fillBuffer(Uri uri, int numberOfFiles) throws Exception {
+        ContentValues values;
+        for (int i = 0; i < numberOfFiles; ++i) {
+            values = createContent(uri);
+            mMediaInserter.insert(uri, values);
+        }
+    }
+
+    @SmallTest
+    public void testInsertContentsLessThanBufferSize() throws Exception {
+        EasyMock.replay(mMockProvider);
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE - 3);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE - 2);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testInsertContentsEqualToBufferSize() throws Exception {
+        EasyMock.expect(mMockProvider.bulkInsert(
+                (Uri) EasyMock.anyObject(), (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(4);
+        EasyMock.replay(mMockProvider);
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE);
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testInsertContentsMoreThanBufferSize() throws Exception {
+        EasyMock.expect(mMockProvider.bulkInsert(
+                (Uri) EasyMock.anyObject(), (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(4);
+        EasyMock.replay(mMockProvider);
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE + 1);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE + 2);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testFlushAllWithEmptyContents() throws Exception {
+        EasyMock.replay(mMockProvider);
+
+        mMediaInserter.flushAll();
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testFlushAllWithSomeContents() throws Exception {
+        EasyMock.expect(mMockProvider.bulkInsert(
+                (Uri) EasyMock.anyObject(), (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(4);
+        EasyMock.replay(mMockProvider);
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE - 3);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE - 2);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
+        mMediaInserter.flushAll();
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testInsertContentsAfterFlushAll() throws Exception {
+        EasyMock.expect(mMockProvider.bulkInsert(
+                (Uri) EasyMock.anyObject(), (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(8);
+        EasyMock.replay(mMockProvider);
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE - 4);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE - 3);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE - 2);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE - 1);
+        mMediaInserter.flushAll();
+
+        fillBuffer(sFilesUri, TEST_BUFFER_SIZE + 1);
+        fillBuffer(sAudioUri, TEST_BUFFER_SIZE + 2);
+        fillBuffer(sVideoUri, TEST_BUFFER_SIZE + 3);
+        fillBuffer(sImagesUri, TEST_BUFFER_SIZE + 4);
+
+        EasyMock.verify(mMockProvider);
+    }
+
+    @SmallTest
+    public void testInsertContentsWithDifferentSizePerContentType() throws Exception {
+        EasyMock.expect(mMockProvider.bulkInsert(MediaUriMatcher.expectMediaUri(sFilesUri),
+                (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(1);
+        EasyMock.expect(mMockProvider.bulkInsert(MediaUriMatcher.expectMediaUri(sAudioUri),
+                (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(2);
+        EasyMock.expect(mMockProvider.bulkInsert(MediaUriMatcher.expectMediaUri(sVideoUri),
+                (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(3);
+        EasyMock.expect(mMockProvider.bulkInsert(MediaUriMatcher.expectMediaUri(sImagesUri),
+                (ContentValues[]) EasyMock.anyObject())).andReturn(1);
+        EasyMock.expectLastCall().times(4);
+        EasyMock.replay(mMockProvider);
+
+        for (int i = 0; i < TEST_BUFFER_SIZE; ++i) {
+            fillBuffer(sFilesUri, 1);
+            fillBuffer(sAudioUri, 2);
+            fillBuffer(sVideoUri, 3);
+            fillBuffer(sImagesUri, 4);
+        }
+
+        EasyMock.verify(mMockProvider);
+    }
+}