MTP: More prototyping work:

New media scanner test program
Media scanner now cleans up after files that no longer exist
Separate database table for audio files
Extract metadata from audio files with libstagefright

Change-Id: I2bd0fe877836c741658e72fcfeb89c11be0d9b41
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk
index 4d86064..d82ace3 100644
--- a/media/mtp/Android.mk
+++ b/media/mtp/Android.mk
@@ -24,6 +24,7 @@
                   MtpDatabase.cpp                       \
                   MtpDataPacket.cpp                     \
                   MtpDebug.cpp                          \
+                  MtpMediaScanner.cpp                   \
                   MtpPacket.cpp                         \
                   MtpRequestPacket.cpp                  \
                   MtpResponsePacket.cpp                 \
@@ -40,7 +41,7 @@
 
 LOCAL_CFLAGS := -DMTP_DEVICE
 
-LOCAL_SHARED_LIBRARIES := libutils libsqlite
+LOCAL_SHARED_LIBRARIES := libutils libsqlite libstagefright
 
 include $(BUILD_EXECUTABLE)
 
@@ -70,4 +71,31 @@
 
 include $(BUILD_HOST_EXECUTABLE)
 
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := scantest
+LOCAL_SRC_FILES:=                                       \
+                  scantest.cpp                          \
+                  MtpMediaScanner.cpp                   \
+                  MtpDatabase.cpp                       \
+                  MtpDataPacket.cpp                     \
+                  MtpPacket.cpp                         \
+                  MtpStringBuffer.cpp                   \
+                  MtpUtils.cpp                          \
+                  SqliteDatabase.cpp                    \
+                  SqliteStatement.cpp                   \
+
+
+#LOCAL_STATIC_LIBRARIES := libusbhost
+#LOCAL_LDLIBS := -lpthread
+
+LOCAL_C_INCLUDES := external/sqlite/dist
+LOCAL_SHARED_LIBRARIES := libutils libsqlite libstagefright
+
+
+LOCAL_CFLAGS := -g
+LOCAL_LDFLAGS := -g
+
+include $(BUILD_EXECUTABLE)
+
 endif
\ No newline at end of file
diff --git a/media/mtp/MtpDatabase.cpp b/media/mtp/MtpDatabase.cpp
index 8f6c75d..ab22ddd 100644
--- a/media/mtp/MtpDatabase.cpp
+++ b/media/mtp/MtpDatabase.cpp
@@ -20,38 +20,74 @@
 #include "SqliteStatement.h"
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <sqlite3.h>
 
 namespace android {
 
-#define ID_COLUMN       1
-#define PATH_COLUMN     2
-#define FORMAT_COLUMN   3
-#define PARENT_COLUMN   4
-#define STORAGE_COLUMN  5
-#define SIZE_COLUMN     6
-#define CREATED_COLUMN  7
-#define MODIFIED_COLUMN 8
+#define FILE_ID_COLUMN                  1
+#define FILE_PATH_COLUMN                2
+#define FILE_FORMAT_COLUMN              3
+#define FILE_PARENT_COLUMN              4
+#define FILE_STORAGE_COLUMN             5
+#define FILE_SIZE_COLUMN                6
+#define FILE_MODIFIED_COLUMN            7
 
-#define TABLE_CREATE    "CREATE TABLE IF NOT EXISTS files ("    \
+#define AUDIO_ID_COLUMN                 1
+#define AUDIO_TITLE_COLUMN              2
+#define AUDIO_ARTIST_COLUMN             3
+#define AUDIO_ALBUM_COLUMN              4
+#define AUDIO_ALBUM_ARTIST_COLUMN       5
+#define AUDIO_GENRE_COLUMN              6
+#define AUDIO_COMPOSER_COLUMN           7
+#define AUDIO_TRACK_NUMBER_COLUMN       8
+#define AUDIO_YEAR_COLUMN               9
+#define AUDIO_DURATION_COLUMN           10
+#define AUDIO_USE_COUNT_COLUMN          11
+#define AUDIO_SAMPLE_RATE_COLUMN        12
+#define AUDIO_NUM_CHANNELS_COLUMN       13
+#define AUDIO_AUDIO_WAVE_CODEC_COLUMN   14
+#define AUDIO_AUDIO_BIT_RATE_COLUMN     15
+
+#define FILE_TABLE_CREATE    "CREATE TABLE IF NOT EXISTS files ("    \
                         "_id INTEGER PRIMARY KEY,"              \
                         "path TEXT,"                            \
                         "format INTEGER,"                       \
                         "parent INTEGER,"                       \
                         "storage INTEGER,"                      \
                         "size INTEGER,"                         \
-                        "date_created INTEGER,"                 \
-                        "date_modified INTEGER"                 \
+                        "date_modified INTEGER"                \
+                        ");"
+
+#define AUDIO_TABLE_CREATE    "CREATE TABLE IF NOT EXISTS audio ("    \
+                        "id INTEGER PRIMARY KEY,"               \
+                        "title TEXT,"                           \
+                        "artist TEXT,"                          \
+                        "album TEXT,"                           \
+                        "album_artist TEXT,"                    \
+                        "genre TEXT,"                           \
+                        "composer TEXT,"                        \
+                        "track_number INTEGER,"                 \
+                        "year INTEGER,"                         \
+                        "duration INTEGER,"                     \
+                        "use_count INTEGER,"                    \
+                        "sample_rate INTEGER,"                  \
+                        "num_channels INTEGER,"                 \
+                        "audio_wave_codec TEXT,"                \
+                        "audio_bit_rate INTEGER"                \
                         ");"
 
 #define PATH_INDEX_CREATE "CREATE INDEX IF NOT EXISTS path_index on files(path);"
 
-#define FILE_ID_QUERY   "SELECT _id FROM files WHERE path = ?;"
+#define FILE_ID_QUERY   "SELECT _id,format FROM files WHERE path = ?;"
 #define FILE_PATH_QUERY "SELECT path,size FROM files WHERE _id = ?"
 
-#define GET_OBJECT_INFO_QUERY   "SELECT storage,format,parent,path,size,date_created,date_modified FROM files WHERE _id = ?;"
-#define FILE_INSERT     "INSERT INTO files VALUES(?,?,?,?,?,?,?,?);"
-#define FILE_DELETE     "DELETE FROM files WHERE path = ?;"
+#define GET_OBJECT_INFO_QUERY   "SELECT storage,format,parent,path,size,date_modified FROM files WHERE _id = ?;"
+#define FILE_INSERT     "INSERT INTO files VALUES(?,?,?,?,?,?,?);"
+#define FILE_DELETE     "DELETE FROM files WHERE _id = ?;"
+
+#define AUDIO_INSERT    "INSERT INTO audio VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
+#define AUDIO_DELETE    "DELETE FROM audio WHERE id = ?;"
 
 struct PropertyTableEntry {
     MtpObjectProperty   property;
@@ -65,7 +101,6 @@
     {   MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT32,    "format"        },
     {   MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR,       "path"          },
     {   MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64,    "size"          },
-    {   MTP_PROPERTY_DATE_CREATED,      MTP_TYPE_STR,       "date_created"  },
     {   MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR,       "date_modified" },
 };
 
@@ -83,11 +118,14 @@
 }
 
 
+
 MtpDatabase::MtpDatabase()
     :   mFileIdQuery(NULL),
         mObjectInfoQuery(NULL),
         mFileInserter(NULL),
-        mFileDeleter(NULL)
+        mFileDeleter(NULL),
+        mAudioInserter(NULL),
+        mAudioDeleter(NULL)
 {
 }
 
@@ -98,66 +136,195 @@
     if (!SqliteDatabase::open(path, create))
         return false;
 
-    // create the table if necessary
-    if (!exec(TABLE_CREATE)) {
-        fprintf(stderr, "could not create table\n");
+    // create tables and indices if necessary
+    if (!exec(FILE_TABLE_CREATE)) {
+        fprintf(stderr, "could not create file table\n");
         return false;
     }
     if (!exec(PATH_INDEX_CREATE)) {
-        fprintf(stderr, "could not path index\n");
+        fprintf(stderr, "could not path index on file table\n");
         return false;
     }
+    if (!exec(AUDIO_TABLE_CREATE)) {
+        fprintf(stderr, "could not create file table\n");
+        return false;
+    }
+
+    if (!mFileIdQuery) {
+        mFileIdQuery = new SqliteStatement(this);
+        if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
+            fprintf(stderr, "could not compile FILE_ID_QUERY\n");
+            exit(-1);
+        }
+    }
+    if (!mFilePathQuery) {
+        mFilePathQuery = new SqliteStatement(this);
+        if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
+            fprintf(stderr, "could not compile FILE_PATH_QUERY\n");
+            exit(-1);
+        }
+    }
+    if (!mObjectInfoQuery) {
+        mObjectInfoQuery = new SqliteStatement(this);
+        if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
+            fprintf(stderr, "could not compile GET_OBJECT_INFO_QUERY\n");
+            exit(-1);
+        }
+    }
+    if (!mFileInserter) {
+        mFileInserter = new SqliteStatement(this);
+        if (!mFileInserter->prepare(FILE_INSERT)) {
+            fprintf(stderr, "could not compile FILE_INSERT\n");
+            exit(-1);
+        }
+    }
+    if (!mFileDeleter) {
+        mFileDeleter = new SqliteStatement(this);
+        if (!mFileDeleter->prepare(FILE_DELETE)) {
+            fprintf(stderr, "could not compile FILE_DELETE\n");
+            exit(-1);
+        }
+    }
+    if (!mAudioInserter) {
+        mAudioInserter = new SqliteStatement(this);
+        if (!mAudioInserter->prepare(AUDIO_INSERT)) {
+            fprintf(stderr, "could not compile AUDIO_INSERT\n");
+            exit(-1);
+        }
+    }
+    if (!mAudioDeleter) {
+        mAudioDeleter = new SqliteStatement(this);
+        if (!mAudioDeleter->prepare(AUDIO_DELETE)) {
+            fprintf(stderr, "could not compile AUDIO_DELETE\n");
+            exit(-1);
+        }
+    }
+
     return true;
 }
 
+uint32_t MtpDatabase::getTableForFile(MtpObjectFormat format) {
+    switch (format) {
+        case MTP_FORMAT_AIFF:
+        case MTP_FORMAT_WAV:
+        case MTP_FORMAT_MP3:
+        case MTP_FORMAT_FLAC:
+        case MTP_FORMAT_UNDEFINED_AUDIO:
+        case MTP_FORMAT_WMA:
+        case MTP_FORMAT_OGG:
+        case MTP_FORMAT_AAC:
+        case MTP_FORMAT_AUDIBLE:
+            return kObjectHandleTableAudio;
+        case MTP_FORMAT_AVI:
+        case MTP_FORMAT_MPEG:
+        case MTP_FORMAT_ASF:
+        case MTP_FORMAT_UNDEFINED_VIDEO:
+        case MTP_FORMAT_WMV:
+        case MTP_FORMAT_MP4_CONTAINER:
+        case MTP_FORMAT_MP2:
+        case MTP_FORMAT_3GP_CONTAINER:
+            return kObjectHandleTableVideo;
+        case MTP_FORMAT_DEFINED:
+        case MTP_FORMAT_EXIF_JPEG:
+        case MTP_FORMAT_TIFF_EP:
+        case MTP_FORMAT_FLASHPIX:
+        case MTP_FORMAT_BMP:
+        case MTP_FORMAT_CIFF:
+        case MTP_FORMAT_GIF:
+        case MTP_FORMAT_JFIF:
+        case MTP_FORMAT_CD:
+        case MTP_FORMAT_PICT:
+        case MTP_FORMAT_PNG:
+        case MTP_FORMAT_TIFF:
+        case MTP_FORMAT_TIFF_IT:
+        case MTP_FORMAT_JP2:
+        case MTP_FORMAT_JPX:
+        case MTP_FORMAT_WINDOWS_IMAGE_FORMAT:
+            return kObjectHandleTableImage;
+        case MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST:
+        case MTP_FORMAT_ABSTRACT_AV_PLAYLIST:
+        case MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST:
+        case MTP_FORMAT_WPL_PLAYLIST:
+        case MTP_FORMAT_M3U_PLAYLIST:
+        case MTP_FORMAT_MPL_PLAYLIST:
+        case MTP_FORMAT_ASX_PLAYLIST:
+        case MTP_FORMAT_PLS_PLAYLIST:
+            return kObjectHandleTablePlaylist;
+        default:
+            return kObjectHandleTableFile;
+    }
+}
+
+MtpObjectHandle MtpDatabase::getObjectHandle(const char* path) {
+    mFileIdQuery->reset();
+    mFileIdQuery->bind(1, path);
+    if (mFileIdQuery->step()) {
+        int row = mFileIdQuery->getColumnInt(0);
+        if (row > 0) {
+            MtpObjectFormat format = mFileIdQuery->getColumnInt(1);
+            row |= getTableForFile(format);
+            return row;
+        }
+    }
+
+    return 0;
+}
+
 MtpObjectHandle MtpDatabase::addFile(const char* path,
                                     MtpObjectFormat format,
                                     MtpObjectHandle parent,
                                     MtpStorageID storage,
                                     uint64_t size,
-                                    time_t created,
                                     time_t modified) {
-
-    // first check to see if the file exists
-    if (mFileIdQuery)
-        mFileIdQuery->reset();
-    else {
-        mFileIdQuery = new SqliteStatement(this);
-        if (!mFileIdQuery->prepare(FILE_ID_QUERY)) {
-            fprintf(stderr, "could not compile FILE_ID_QUERY\n");
-            delete mFileIdQuery;
-            mFileIdQuery = NULL;
-            return kInvalidObjectHandle;
-        }
-    }
-
-    mFileIdQuery->bind(1, path);
-    if (mFileIdQuery->step()) {
-        int row = mFileIdQuery->getColumnInt(0);
-        if (row > 0)
-            return row;
-    }
-
-    if (!mFileInserter) {
-        mFileInserter = new SqliteStatement(this);
-        if (!mFileInserter->prepare(FILE_INSERT)) {
-            fprintf(stderr, "could not compile FILE_INSERT\n");
-            delete mFileInserter;
-            mFileInserter = NULL;
-            return kInvalidObjectHandle;
-        }
-    }
-    mFileInserter->bind(PATH_COLUMN, path);
-    mFileInserter->bind(FORMAT_COLUMN, format);
-    mFileInserter->bind(PARENT_COLUMN, parent);
-    mFileInserter->bind(STORAGE_COLUMN, storage);
-    mFileInserter->bind(SIZE_COLUMN, size);
-    mFileInserter->bind(CREATED_COLUMN, created);
-    mFileInserter->bind(MODIFIED_COLUMN, modified);
+    mFileInserter->bind(FILE_PATH_COLUMN, path);
+    mFileInserter->bind(FILE_FORMAT_COLUMN, format);
+    mFileInserter->bind(FILE_PARENT_COLUMN, parent);
+    mFileInserter->bind(FILE_STORAGE_COLUMN, storage);
+    mFileInserter->bind(FILE_SIZE_COLUMN, size);
+    mFileInserter->bind(FILE_MODIFIED_COLUMN, modified);
     mFileInserter->step();
     mFileInserter->reset();
-    int row = lastInsertedRow();
-    return (row > 0 ? row : kInvalidObjectHandle);
+    int result = lastInsertedRow();
+    return (result <= 0 ? kInvalidObjectHandle : result);
+}
+
+MtpObjectHandle MtpDatabase::addAudioFile(MtpObjectHandle handle) {
+    mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
+    mAudioInserter->step();
+    mAudioInserter->reset();
+    int result = lastInsertedRow();
+    handle |= kObjectHandleTableAudio;
+    return (result > 0 ? handle : kInvalidObjectHandle);
+}
+
+MtpObjectHandle MtpDatabase::addAudioFile(MtpObjectHandle handle,
+                                    const char* title,
+                                    const char* artist,
+                                    const char* album,
+                                    const char* albumArtist,
+                                    const char* genre,
+                                    const char* composer,
+                                    const char* mimeType,
+                                    int track,
+                                    int year,
+                                    int duration) {
+    mAudioInserter->bind(AUDIO_ID_COLUMN, handle);
+    if (title) mAudioInserter->bind(AUDIO_TITLE_COLUMN, title);
+    if (artist) mAudioInserter->bind(AUDIO_ARTIST_COLUMN, artist);
+    if (album) mAudioInserter->bind(AUDIO_ALBUM_COLUMN, album);
+    if (albumArtist) mAudioInserter->bind(AUDIO_ALBUM_ARTIST_COLUMN, albumArtist);
+    if (genre) mAudioInserter->bind(AUDIO_GENRE_COLUMN, genre);
+    if (composer) mAudioInserter->bind(AUDIO_COMPOSER_COLUMN, composer);
+    if (track) mAudioInserter->bind(AUDIO_TRACK_NUMBER_COLUMN, track);
+    if (year) mAudioInserter->bind(AUDIO_YEAR_COLUMN, year);
+    if (duration) mAudioInserter->bind(AUDIO_DURATION_COLUMN, duration);
+    mAudioInserter->step();
+    mAudioInserter->reset();
+    int result = lastInsertedRow();
+    if (result <= 0)
+        return kInvalidObjectHandle;
+    result |= kObjectHandleTableAudio;
+    return result;
 }
 
 MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID,
@@ -168,7 +335,7 @@
     bool                whereParent = (parent != 0);
     char                intBuffer[20];
 
-    MtpString  query("SELECT _id FROM files");
+    MtpString  query("SELECT _id,format FROM files");
     if (whereStorage || whereFormat || whereParent)
         query += " WHERE";
     if (whereStorage) {
@@ -184,6 +351,8 @@
         query += intBuffer;
     }
     if (whereParent) {
+        if (parent != MTP_PARENT_ROOT)
+            parent &= kObjectHandleIndexMask;
         snprintf(intBuffer, sizeof(intBuffer), "%d", parent);
         if (whereStorage || whereFormat)
             query += " AND";
@@ -201,14 +370,18 @@
         if (stmt.step()) {
             int index = stmt.getColumnInt(0);
             printf("stmt.getColumnInt returned %d\n", index);
-            if (index > 0)
+            if (index > 0) {
+                MtpObjectFormat format = stmt.getColumnInt(1);
+                index |= getTableForFile(format);
                 list->push(index);
+            }
         }
     }
     printf("list size: %d\n", list->size());
     return list;
 }
 
+
 MtpResponseCode MtpDatabase::getObjectProperty(MtpObjectHandle handle,
                                     MtpObjectProperty property,
                                     MtpDataPacket& packet) {
@@ -216,6 +389,9 @@
     const char* columnName;
     char        intBuffer[20];
 
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
+
     if (!getPropertyInfo(property, type, columnName))
         return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
     snprintf(intBuffer, sizeof(intBuffer), "%d", handle);
@@ -272,18 +448,10 @@
                                         MtpDataPacket& packet) {
     char    date[20];
 
-    if (mObjectInfoQuery)
-        mObjectInfoQuery->reset();
-    else {
-        mObjectInfoQuery = new SqliteStatement(this);
-        if (!mObjectInfoQuery->prepare(GET_OBJECT_INFO_QUERY)) {
-            fprintf(stderr, "could not compile FILE_ID_QUERY\n");
-            delete mObjectInfoQuery;
-            mObjectInfoQuery = NULL;
-            return MTP_RESPONSE_GENERAL_ERROR;
-        }
-    }
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
 
+    mObjectInfoQuery->reset();
     mObjectInfoQuery->bind(1, handle);
     if (!mObjectInfoQuery->step())
         return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
@@ -297,8 +465,7 @@
     if (lastSlash)
         name = lastSlash + 1;
     int64_t size = mObjectInfoQuery->getColumnInt64(4);
-    time_t created = mObjectInfoQuery->getColumnInt(5);
-    time_t modified = mObjectInfoQuery->getColumnInt(6);
+    time_t modified = mObjectInfoQuery->getColumnInt(5);
     int associationType = (format == MTP_FORMAT_ASSOCIATION ?
                             MTP_ASSOCIATION_TYPE_GENERIC_FOLDER :
                             MTP_ASSOCIATION_TYPE_UNDEFINED);
@@ -321,8 +488,7 @@
     packet.putUInt32(0);   // association desc
     packet.putUInt32(0);   // sequence number
     packet.putString(name);   // file name
-    formatDateTime(created, date, sizeof(date));
-    packet.putString(date);   // date created
+    packet.putEmptyString();
     formatDateTime(modified, date, sizeof(date));
     packet.putString(date);   // date modified
     packet.putEmptyString();   // keywords
@@ -333,18 +499,9 @@
 bool MtpDatabase::getObjectFilePath(MtpObjectHandle handle,
                                     MtpString& filePath,
                                     int64_t& fileLength) {
-    if (mFilePathQuery)
-        mFilePathQuery->reset();
-    else {
-        mFilePathQuery = new SqliteStatement(this);
-        if (!mFilePathQuery->prepare(FILE_PATH_QUERY)) {
-            fprintf(stderr, "could not compile FILE_ID_QUERY\n");
-            delete mFilePathQuery;
-            mFilePathQuery = NULL;
-            return kInvalidObjectHandle;
-        }
-    }
-
+    if (handle != MTP_PARENT_ROOT)
+        handle &= kObjectHandleIndexMask;
+    mFilePathQuery->reset();
     mFilePathQuery->bind(1, handle);
     if (!mFilePathQuery->step())
         return false;
@@ -358,22 +515,52 @@
 }
 
 bool MtpDatabase::deleteFile(MtpObjectHandle handle) {
-    if (!mFileDeleter) {
-        mFileDeleter = new SqliteStatement(this);
-        if (!mFileDeleter->prepare(FILE_DELETE)) {
-            fprintf(stderr, "could not compile FILE_DELETE\n");
-            delete mFileDeleter;
-            mFileDeleter = NULL;
-            return false;
-        }
-    }
-printf("deleteFile %d\n", handle);
+    uint32_t table = handle & kObjectHandleTableMask;
+    handle &= kObjectHandleIndexMask;
     mFileDeleter->bind(1, handle);
     mFileDeleter->step();
     mFileDeleter->reset();
+    if (table == kObjectHandleTableAudio) {
+        mAudioDeleter->bind(1, handle);
+        mAudioDeleter->step();
+        mAudioDeleter->reset();
+    }
+
     return true;
 }
 
+MtpObjectHandle* MtpDatabase::getFileList(int& outCount) {
+    MtpObjectHandle* result = NULL;
+    int count = 0;
+    SqliteStatement stmt(this);
+    stmt.prepare("SELECT count(*) FROM files;");
+
+    MtpObjectHandleList* list = new MtpObjectHandleList();
+    if (stmt.step())
+        count = stmt.getColumnInt(0);
+
+    if (count > 0) {
+        result = new MtpObjectHandle[count];
+        memset(result, 0, count * sizeof(*result));
+        SqliteStatement stmt2(this);
+        stmt2.prepare("SELECT _id,format FROM files;");
+
+        for (int i = 0; i < count; i++) {
+            if (!stmt2.step()) {
+                printf("getFileList ended early\n");
+                count = i;
+                break;
+            }
+            MtpObjectHandle handle = stmt2.getColumnInt(0);
+            MtpObjectFormat format = stmt2.getColumnInt(1);
+            handle |= getTableForFile(format);
+            result[i] = handle;
+        }
+    }
+    outCount = count;
+    return result;
+}
+
 /*
     for getObjectPropDesc
 
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index 2a48155..a6be6a6 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -33,20 +33,38 @@
     SqliteStatement*        mObjectInfoQuery;
     SqliteStatement*        mFileInserter;
     SqliteStatement*        mFileDeleter;
+    SqliteStatement*        mAudioInserter;
+    SqliteStatement*        mAudioDeleter;
 
 public:
                             MtpDatabase();
     virtual                 ~MtpDatabase();
 
+    static uint32_t         getTableForFile(MtpObjectFormat format);
+
     bool                    open(const char* path, bool create);
+    MtpObjectHandle         getObjectHandle(const char* path);
     MtpObjectHandle         addFile(const char* path,
                                     MtpObjectFormat format,
                                     MtpObjectHandle parent,
                                     MtpStorageID storage,
                                     uint64_t size,
-                                    time_t created,
                                     time_t modified);
 
+    MtpObjectHandle         addAudioFile(MtpObjectHandle id);
+
+    MtpObjectHandle         addAudioFile(MtpObjectHandle id,
+                                    const char* title,
+                                    const char* artist,
+                                    const char* album,
+                                    const char* albumArtist,
+                                    const char* genre,
+                                    const char* composer,
+                                    const char* mimeType,
+                                    int track,
+                                    int year,
+                                    int duration);
+
     MtpObjectHandleList*    getObjectList(MtpStorageID storageID,
                                     MtpObjectFormat format,
                                     MtpObjectHandle parent);
@@ -62,6 +80,9 @@
                                     MtpString& filePath,
                                     int64_t& fileLength);
     bool                    deleteFile(MtpObjectHandle handle);
+
+    // helper for media scanner
+    MtpObjectHandle*        getFileList(int& outCount);
 };
 
 }; // namespace android
diff --git a/media/mtp/MtpMediaScanner.cpp b/media/mtp/MtpMediaScanner.cpp
new file mode 100644
index 0000000..8b08f36
--- /dev/null
+++ b/media/mtp/MtpMediaScanner.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include "MtpDatabase.h"
+#include "MtpMediaScanner.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include <media/mediascanner.h>
+#include <media/stagefright/StagefrightMediaScanner.h>
+
+namespace android {
+
+class MtpMediaScannerClient : public MediaScannerClient
+{
+public:
+    MtpMediaScannerClient()
+    {
+        reset();
+    }
+
+    virtual ~MtpMediaScannerClient()
+    {
+    }
+
+    // returns true if it succeeded, false if an exception occured in the Java code
+    virtual bool scanFile(const char* path, long long lastModified, long long fileSize)
+    {
+        printf("scanFile %s\n", path);
+        return true;
+    }
+
+    // returns true if it succeeded, false if an exception occured in the Java code
+    virtual bool handleStringTag(const char* name, const char* value)
+    {
+        int temp;
+
+        if (!strcmp(name, "title")) {
+            mTitle = value;
+            mHasTitle = true;
+        } else if  (!strcmp(name, "artist")) {
+            mArtist = value;
+            mHasArtist = true;
+        } else if  (!strcmp(name, "album")) {
+            mAlbum = value;
+            mHasAlbum = true;
+        } else if  (!strcmp(name, "albumartist")) {
+            mAlbumArtist = value;
+            mHasAlbumArtist = true;
+        } else if  (!strcmp(name, "genre")) {
+            // FIXME - handle numeric values here
+            mGenre = value;
+            mHasGenre = true;
+        } else if  (!strcmp(name, "composer")) {
+            mComposer = value;
+            mHasComposer = true;
+        } else if  (!strcmp(name, "tracknumber")) {
+            if (sscanf(value, "%d", &temp) == 1)
+                mTrack = temp;
+        } else if  (!strcmp(name, "discnumber")) {
+            // currently unused
+        } else if  (!strcmp(name, "year") || !strcmp(name, "date")) {
+            if (sscanf(value, "%d", &temp) == 1)
+                mYear = temp;
+        } else if  (!strcmp(name, "duration")) {
+            if (sscanf(value, "%d", &temp) == 1)
+                mDuration = temp;
+        } else {
+            printf("handleStringTag %s : %s\n", name, value);
+        }
+        return true;
+    }
+
+    // returns true if it succeeded, false if an exception occured in the Java code
+    virtual bool setMimeType(const char* mimeType)
+    {
+        mMimeType = mimeType;
+        mHasMimeType = true;
+        return true;
+    }
+
+    // returns true if it succeeded, false if an exception occured in the Java code
+    virtual bool addNoMediaFolder(const char* path)
+    {
+        printf("addNoMediaFolder %s\n", path);
+        return true;
+    }
+
+    void reset()
+    {
+        mHasTitle = false;
+        mHasArtist = false;
+        mHasAlbum = false;
+        mHasAlbumArtist = false;
+        mHasGenre = false;
+        mHasComposer = false;
+        mHasMimeType = false;
+        mTrack = mYear = mDuration = 0;
+    }
+
+    inline const char* getTitle() const { return mHasTitle ? (const char *)mTitle : NULL; }
+    inline const char* getArtist() const { return mHasArtist ? (const char *)mArtist : NULL; }
+    inline const char* getAlbum() const { return mHasAlbum ? (const char *)mAlbum : NULL; }
+    inline const char* getAlbumArtist() const { return mHasAlbumArtist ? (const char *)mAlbumArtist : NULL; }
+    inline const char* getGenre() const { return mHasGenre ? (const char *)mGenre : NULL; }
+    inline const char* getComposer() const { return mHasComposer ? (const char *)mComposer : NULL; }
+    inline const char* getMimeType() const { return mHasMimeType ? (const char *)mMimeType : NULL; }
+    inline int getTrack() const { return mTrack; }
+    inline int getYear() const { return mYear; }
+    inline int getDuration() const { return mDuration; }
+
+private:
+    MtpString   mTitle;
+    MtpString   mArtist;
+    MtpString   mAlbum;
+    MtpString   mAlbumArtist;
+    MtpString   mGenre;
+    MtpString   mComposer;
+    MtpString   mMimeType;
+
+    bool        mHasTitle;
+    bool        mHasArtist;
+    bool        mHasAlbum;
+    bool        mHasAlbumArtist;
+    bool        mHasGenre;
+    bool        mHasComposer;
+    bool        mHasMimeType;
+
+    int         mTrack;
+    int         mYear;
+    int         mDuration;
+};
+
+
+MtpMediaScanner::MtpMediaScanner(MtpStorageID id, const char* filePath, MtpDatabase* db)
+    :   mStorageID(id),
+        mFilePath(filePath),
+        mDatabase(db),
+        mMediaScanner(NULL),
+        mMediaScannerClient(NULL),
+        mFileList(NULL),
+        mFileCount(0)
+{
+    mMediaScanner = new StagefrightMediaScanner;
+    mMediaScannerClient = new MtpMediaScannerClient;
+}
+
+MtpMediaScanner::~MtpMediaScanner() {
+}
+
+bool MtpMediaScanner::scanFiles() {
+    mDatabase->beginTransaction();
+    mFileCount = 0;
+    mFileList = mDatabase->getFileList(mFileCount);
+
+    int ret = scanDirectory(mFilePath, MTP_PARENT_ROOT);
+
+    for (int i = 0; i < mFileCount; i++) {
+        MtpObjectHandle test = mFileList[i];
+        if (! (test & kObjectHandleMarkBit)) {
+            printf("delete missing file %08X\n", test);
+            mDatabase->deleteFile(test);
+        }
+    }
+
+    delete[] mFileList;
+    mFileCount = 0;
+    mDatabase->commitTransaction();
+    return (ret == 0);
+}
+
+
+static const struct MediaFileTypeEntry
+{
+    const char*     extension;
+    MtpObjectFormat format;
+    uint32_t        table;
+} sFileTypes[] =
+{
+    { "MP3",    MTP_FORMAT_MP3,             kObjectHandleTableAudio     },
+    { "M4A",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "WAV",    MTP_FORMAT_WAV,             kObjectHandleTableAudio     },
+    { "AMR",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "AWB",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "WMA",    MTP_FORMAT_WMA,             kObjectHandleTableAudio     },
+    { "OGG",    MTP_FORMAT_OGG,             kObjectHandleTableAudio     },
+    { "OGA",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "AAC",    MTP_FORMAT_AAC,             kObjectHandleTableAudio     },
+    { "MID",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "MIDI",   MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "XMF",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "RTTTL",  MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "SMF",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "IMY",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "RTX",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "OTA",    MTP_FORMAT_UNDEFINED_AUDIO, kObjectHandleTableAudio     },
+    { "MPEG",   MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "MP4",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "M4V",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "3GP",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "3GPP",   MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "3G2",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "3GPP2",  MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "WMV",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "ASF",    MTP_FORMAT_UNDEFINED_VIDEO, kObjectHandleTableVideo     },
+    { "JPG",    MTP_FORMAT_EXIF_JPEG,       kObjectHandleTableImage     },
+    { "JPEG",   MTP_FORMAT_EXIF_JPEG,       kObjectHandleTableImage     },
+    { "GIF",    MTP_FORMAT_GIF,             kObjectHandleTableImage     },
+    { "PNG",    MTP_FORMAT_PNG,             kObjectHandleTableImage     },
+    { "BMP",    MTP_FORMAT_BMP,             kObjectHandleTableImage     },
+    { "WBMP",   MTP_FORMAT_BMP,             kObjectHandleTableImage     },
+    { "M3U",    MTP_FORMAT_M3U_PLAYLIST,    kObjectHandleTablePlaylist  },
+    { "PLS",    MTP_FORMAT_PLS_PLAYLIST,    kObjectHandleTablePlaylist  },
+    { "WPL",    MTP_FORMAT_WPL_PLAYLIST,    kObjectHandleTablePlaylist  },
+};
+
+MtpObjectFormat MtpMediaScanner::getFileFormat(const char* path, uint32_t& table)
+{
+    const char* extension = strrchr(path, '.');
+    if (!extension)
+        return MTP_FORMAT_UNDEFINED;
+    extension++; // skip the dot
+
+    for (unsigned i = 0; i < sizeof(sFileTypes) / sizeof(sFileTypes[0]); i++) {
+        if (!strcasecmp(extension, sFileTypes[i].extension)) {
+            table = sFileTypes[i].table;
+            return sFileTypes[i].format;
+        }
+    }
+    table = kObjectHandleTableFile;
+    return MTP_FORMAT_UNDEFINED;
+}
+
+int MtpMediaScanner::scanDirectory(const char* path, MtpObjectHandle parent)
+{
+    char buffer[PATH_MAX];
+    struct dirent* entry;
+
+    unsigned length = strlen(path);
+    if (length > sizeof(buffer) + 2) {
+        fprintf(stderr, "path too long: %s\n", path);
+    }
+
+    DIR* dir = opendir(path);
+    if (!dir) {
+        fprintf(stderr, "opendir %s failed, errno: %d", path, errno);
+        return -1;
+    }
+
+    strncpy(buffer, path, sizeof(buffer));
+    char* fileStart = buffer + length;
+    // make sure we have a trailing slash
+    if (fileStart[-1] != '/') {
+        *(fileStart++) = '/';
+    }
+    int fileNameLength = sizeof(buffer) + fileStart - buffer;
+
+    while ((entry = readdir(dir))) {
+        const char* name = entry->d_name;
+
+        // ignore "." and "..", as well as any files or directories staring with dot
+        if (name[0] == '.') {
+            continue;
+        }
+        if (strlen(name) + 1 > fileNameLength) {
+            fprintf(stderr, "path too long for %s\n", name);
+            continue;
+        }
+        strcpy(fileStart, name);
+
+        struct stat statbuf;
+        memset(&statbuf, 0, sizeof(statbuf));
+        stat(buffer, &statbuf);
+
+        if (entry->d_type == DT_DIR) {
+            MtpObjectHandle handle = mDatabase->getObjectHandle(buffer);
+            if (handle) {
+                markFile(handle);
+            } else {
+                handle = mDatabase->addFile(buffer, MTP_FORMAT_ASSOCIATION,
+                        parent, mStorageID, 0, statbuf.st_mtime);
+            }
+            scanDirectory(buffer, handle);
+        } else if (entry->d_type == DT_REG) {
+            scanFile(buffer, parent, statbuf);
+        }
+    }
+
+    closedir(dir);
+    return 0;
+}
+
+void MtpMediaScanner::scanFile(const char* path, MtpObjectHandle parent, struct stat& statbuf) {
+    uint32_t table;
+    MtpObjectFormat format = getFileFormat(path, table);
+    // don't scan unknown file types
+    if (format == MTP_FORMAT_UNDEFINED)
+        return;
+    MtpObjectHandle handle = mDatabase->getObjectHandle(path);
+    // fixme - rescan if mod date changed
+    if (handle) {
+        markFile(handle);
+    } else {
+        mDatabase->beginTransaction();
+        handle = mDatabase->addFile(path, format, parent, mStorageID,
+                statbuf.st_size, statbuf.st_mtime);
+        if (handle <= 0) {
+            fprintf(stderr, "addFile failed in MtpMediaScanner::scanFile()\n");
+            mDatabase->rollbackTransaction();
+            return;
+        }
+
+        if (table == kObjectHandleTableAudio) {
+            mMediaScannerClient->reset();
+            mMediaScanner->processFile(path, NULL, *mMediaScannerClient);
+            handle = mDatabase->addAudioFile(handle,
+                    mMediaScannerClient->getTitle(),
+                    mMediaScannerClient->getArtist(),
+                    mMediaScannerClient->getAlbum(),
+                    mMediaScannerClient->getAlbumArtist(),
+                    mMediaScannerClient->getGenre(),
+                    mMediaScannerClient->getComposer(),
+                    mMediaScannerClient->getMimeType(),
+                    mMediaScannerClient->getTrack(),
+                    mMediaScannerClient->getYear(),
+                    mMediaScannerClient->getDuration());
+        }
+        mDatabase->commitTransaction();
+    }
+}
+
+void MtpMediaScanner::markFile(MtpObjectHandle handle) {
+    if (mFileList) {
+        handle &= kObjectHandleIndexMask;
+        // binary search for the file in mFileList
+        int low = 0;
+        int high = mFileCount;
+        int index;
+
+        while (low < high) {
+            index = (low + high) >> 1;
+            MtpObjectHandle test = (mFileList[index] & kObjectHandleIndexMask);
+            if (handle < test)
+                high = index;       // item is less than index
+            else if (handle > test)
+                low = index + 1;    // item is greater than index
+            else {
+                mFileList[index] |= kObjectHandleMarkBit;
+                return;
+            }
+        }
+        fprintf(stderr, "file %d not found in mFileList\n", handle);
+    }
+}
+
+}  // namespace android
diff --git a/media/mtp/MtpMediaScanner.h b/media/mtp/MtpMediaScanner.h
new file mode 100644
index 0000000..53d5063
--- /dev/null
+++ b/media/mtp/MtpMediaScanner.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef _MTP_MEDIA_SCANNER_H
+#define _MTP_MEDIA_SCANNER_H
+
+struct stat;
+
+namespace android {
+
+class MtpDatabase;
+class SqliteStatement;
+class MediaScanner;
+class MtpMediaScannerClient;
+
+class MtpMediaScanner {
+private:
+    MtpStorageID            mStorageID;
+    const char*             mFilePath;
+    MtpDatabase*            mDatabase;
+    MediaScanner*           mMediaScanner;
+    MtpMediaScannerClient*  mMediaScannerClient;
+
+    // for garbage collecting missing files
+    MtpObjectHandle*        mFileList;
+    int                     mFileCount;
+
+public:
+                            MtpMediaScanner(MtpStorageID id, const char* filePath, MtpDatabase* db);
+    virtual                 ~MtpMediaScanner();
+
+    bool                    scanFiles();
+
+private:
+    MtpObjectFormat         getFileFormat(const char* path, uint32_t& table);
+    int                     scanDirectory(const char* path, MtpObjectHandle parent);
+    void                    scanFile(const char* path, MtpObjectHandle parent, struct stat& statbuf);
+    void                    markFile(MtpObjectHandle handle);
+};
+
+}; // namespace android
+
+#endif // _MTP_MEDIA_SCANNER_H
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index d868926..6a90568 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -79,9 +79,35 @@
 };
 
 static const MtpObjectFormat kSupportedPlaybackFormats[] = {
-    // FIXME - fill this out later
+    // MTP_FORMAT_UNDEFINED,
     MTP_FORMAT_ASSOCIATION,
+    // MTP_FORMAT_TEXT,
+    // MTP_FORMAT_HTML,
     MTP_FORMAT_MP3,
+    //MTP_FORMAT_AVI,
+    MTP_FORMAT_MPEG,
+    // MTP_FORMAT_ASF,
+    MTP_FORMAT_EXIF_JPEG,
+    MTP_FORMAT_TIFF_EP,
+    // MTP_FORMAT_BMP,
+    MTP_FORMAT_GIF,
+    MTP_FORMAT_JFIF,
+    MTP_FORMAT_PNG,
+    MTP_FORMAT_TIFF,
+    MTP_FORMAT_WMA,
+    MTP_FORMAT_OGG,
+    MTP_FORMAT_AAC,
+    // MTP_FORMAT_FLAC,
+    // MTP_FORMAT_WMV,
+    MTP_FORMAT_MP4_CONTAINER,
+    MTP_FORMAT_MP2,
+    MTP_FORMAT_3GP_CONTAINER,
+    // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM,
+    // MTP_FORMAT_ABSTRACT_AV_PLAYLIST,
+    // MTP_FORMAT_WPL_PLAYLIST,
+    // MTP_FORMAT_M3U_PLAYLIST,
+    // MTP_FORMAT_MPL_PLAYLIST,
+    // MTP_FORMAT_PLS_PLAYLIST,
 };
 
 MtpServer::MtpServer(int fd, const char* databasePath)
@@ -420,9 +446,7 @@
     mData.getString(modified);     // date modified
     // keywords follow
 
-    time_t createdTime, modifiedTime;
-    if (!parseDateTime(created, createdTime))
-        createdTime = 0;
+    time_t modifiedTime;
     if (!parseDateTime(modified, modifiedTime))
         modifiedTime = 0;
 printf("SendObjectInfo format: %04X size: %d name: %s, created: %s, modified: %s\n",
@@ -432,11 +456,17 @@
         path += "/";
     path += (const char *)name;
 
-    MtpObjectHandle handle = mDatabase->addFile((const char*)path,
-                                    format, parent, storageID, mSendObjectFileSize,
-                                    createdTime, modifiedTime);
-    if (handle == kInvalidObjectHandle)
+    mDatabase->beginTransaction();
+    MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID, 
+                                    mSendObjectFileSize, modifiedTime);
+    if (handle == kInvalidObjectHandle) {
+        mDatabase->rollbackTransaction();
         return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    uint32_t table = MtpDatabase::getTableForFile(format);
+    if (table == kObjectHandleTableAudio)
+        handle = mDatabase->addAudioFile(handle);
+    mDatabase->commitTransaction();
 
   if (format == MTP_FORMAT_ASSOCIATION) {
         mode_t mask = umask(0);
diff --git a/media/mtp/MtpStorage.cpp b/media/mtp/MtpStorage.cpp
index d4de819..f176148 100644
--- a/media/mtp/MtpStorage.cpp
+++ b/media/mtp/MtpStorage.cpp
@@ -16,6 +16,7 @@
 
 #include "MtpDatabase.h"
 #include "MtpStorage.h"
+#include "MtpMediaScanner.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -74,65 +75,8 @@
 }
 
 bool MtpStorage::scanFiles() {
-    mDatabase->beginTransaction();
-    int ret = scanDirectory(mFilePath, MTP_PARENT_ROOT);
-    mDatabase->commitTransaction();
-    return (ret == 0);
-}
-
-int MtpStorage::scanDirectory(const char* path, MtpObjectHandle parent)
-{
-    char buffer[PATH_MAX];
-    struct dirent* entry;
-
-    int length = strlen(path);
-    if (length > sizeof(buffer) + 2) {
-        fprintf(stderr, "path too long: %s\n", path);
-    }
-
-    DIR* dir = opendir(path);
-    if (!dir) {
-        fprintf(stderr, "opendir %s failed, errno: %d", path, errno);
-        return -1;
-    }
-
-    strncpy(buffer, path, sizeof(buffer));
-    char* fileStart = buffer + length;
-    // make sure we have a trailing slash
-    if (fileStart[-1] != '/') {
-        *(fileStart++) = '/';
-    }
-    int fileNameLength = sizeof(buffer) + fileStart - buffer;
-
-    while ((entry = readdir(dir))) {
-        const char* name = entry->d_name;
-
-        // ignore "." and "..", as well as any files or directories staring with dot
-        if (name[0] == '.') {
-            continue;
-        }
-        if (strlen(name) + 1 > fileNameLength) {
-            fprintf(stderr, "path too long for %s\n", name);
-            continue;
-        }
-        strcpy(fileStart, name);
-
-        struct stat statbuf;
-        memset(&statbuf, 0, sizeof(statbuf));
-        stat(buffer, &statbuf);
-
-        if (entry->d_type == DT_DIR) {
-            MtpObjectHandle handle = mDatabase->addFile(buffer, MTP_FORMAT_ASSOCIATION,
-                    parent, mStorageID, 0, 0, statbuf.st_mtime);
-            scanDirectory(buffer, handle);
-        } else if (entry->d_type == DT_REG) {
-            mDatabase->addFile(buffer, MTP_FORMAT_UNDEFINED, parent, mStorageID,
-                    statbuf.st_size, 0, statbuf.st_mtime);
-        }
-    }
-
-    closedir(dir);
-    return 0;
+    MtpMediaScanner scanner(mStorageID, mFilePath, mDatabase);
+    return scanner.scanFiles();
 }
 
 }  // namespace android
diff --git a/media/mtp/MtpStorage.h b/media/mtp/MtpStorage.h
index b1d4408..6097272 100644
--- a/media/mtp/MtpStorage.h
+++ b/media/mtp/MtpStorage.h
@@ -46,9 +46,6 @@
     inline const char*      getPath() const { return mFilePath; }
 
     bool                    scanFiles();
-
-private:
-    int                     scanDirectory(const char* path, MtpObjectHandle parent);
 };
 
 }; // namespace android
diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h
index 27abaa7..57a0281 100644
--- a/media/mtp/mtp.h
+++ b/media/mtp/mtp.h
@@ -32,7 +32,17 @@
 // values 0x00000000 and 0xFFFFFFFF are reserved for special purposes.
 typedef uint32_t MtpObjectHandle;
 
-#define kInvalidObjectHandle 0xFFFFFFFF
+#define kInvalidObjectHandle    0xFFFFFFFF
+
+// MtpObjectHandle bits and masks
+#define kObjectHandleMarkBit        0x80000000      // used for mark & sweep by MtpMediaScanner
+#define kObjectHandleTableMask      0x70000000      // mask for object table
+#define kObjectHandleTableFile      0x00000000      // object is only in the file table
+#define kObjectHandleTableAudio     0x10000000      // object is in the audio table
+#define kObjectHandleTableVideo     0x20000000      // object is in the video table
+#define kObjectHandleTableImage     0x30000000      // object is in the images table
+#define kObjectHandleTablePlaylist  0x40000000      // object is in the playlist table
+#define kObjectHandleIndexMask      0x0FFFFFFF      // mask for object index in file table
 
 #define MTP_STANDARD_VERSION            100
 
diff --git a/media/mtp/scantest.cpp b/media/mtp/scantest.cpp
new file mode 100644
index 0000000..f910bb6
--- /dev/null
+++ b/media/mtp/scantest.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <stdio.h>
+
+#include "MtpDatabase.h"
+#include "MtpMediaScanner.h"
+
+using namespace android;
+
+int main(int argc, char* argv[]) {
+    if (argc != 2) {
+        fprintf(stderr, "usage: %s <storage path>\n", argv[0]);
+        return -1;
+    }
+
+    MtpDatabase* database = new MtpDatabase();
+    database->open("scantest.db", true);
+
+    MtpMediaScanner scanner(1, argv[1], database);
+    scanner.scanFiles();
+    database->close();
+
+    return 0;
+}