am 411252cd: Merge "Guarantee that db, shm and wal files are deleted together."

* commit '411252cd75a29c7e184ee13cd15e8598851afb64':
  Guarantee that db, shm and wal files are deleted together.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 25265d9..693d20c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2,7 +2,7 @@
         package="com.android.providers.media"
         android:sharedUserId="android.media"
         android:sharedUserLabel="@string/uid_label"
-        android:versionCode="512">
+        android:versionCode="601">
         
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index af9e5f8..3179ba6 100755
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -102,6 +102,7 @@
 import java.util.Stack;
 
 import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
 import libcore.io.Libcore;
 
 /**
@@ -566,8 +567,6 @@
         iFilter.addDataScheme("file");
         context.registerReceiver(mUnmountReceiver, iFilter);
 
-        mCaseInsensitivePaths = true;
-
         StorageManager storageManager =
                 (StorageManager)context.getSystemService(Context.STORAGE_SERVICE);
         mExternalStoragePaths = storageManager.getVolumePaths();
@@ -1574,6 +1573,8 @@
                     + FileColumns.MEDIA_TYPE_IMAGE + ";");
         }
 
+        // Honeycomb went up to version 307, ICS started at 401
+
         // Database version 401 did not add storage_id to the internal database.
         // We need it there too, so add it in version 402
         if (fromVersion < 401 || (fromVersion == 401 && internal)) {
@@ -1643,6 +1644,8 @@
             db.execSQL("DELETE FROM audio_genres");
         }
 
+        // ICS went out with database version 409, JB started at 500
+
         if (fromVersion < 500) {
             // we're now deleting the file in mediaprovider code, rather than via a trigger
             db.execSQL("DROP TRIGGER IF EXISTS videothumbnails_cleanup;");
@@ -1750,11 +1753,65 @@
             updateBucketNames(db);
         }
 
-        if (fromVersion < 512) {
+        // JB 4.2 went out with database version 511, starting next release with 600
+
+        if (fromVersion < 600) {
+            // modify _data column to be unique and collate nocase. Because this drops the original
+            // table and replaces it with a new one by the same name, we need to also recreate all
+            // indices and triggers that refer to the files table.
+            // Views don't need to be recreated.
+
+            db.execSQL("CREATE TABLE files2 (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    "_data TEXT UNIQUE" +
+                    // the internal filesystem is case-sensitive
+                    (internal ? "," : " COLLATE NOCASE,") +
+                    "_size INTEGER,format INTEGER,parent INTEGER,date_added INTEGER," +
+                    "date_modified INTEGER,mime_type TEXT,title TEXT,description TEXT," +
+                    "_display_name TEXT,picasa_id TEXT,orientation INTEGER,latitude DOUBLE," +
+                    "longitude DOUBLE,datetaken INTEGER,mini_thumb_magic INTEGER,bucket_id TEXT," +
+                    "bucket_display_name TEXT,isprivate INTEGER,title_key TEXT,artist_id INTEGER," +
+                    "album_id INTEGER,composer TEXT,track INTEGER,year INTEGER CHECK(year!=0)," +
+                    "is_ringtone INTEGER,is_music INTEGER,is_alarm INTEGER," +
+                    "is_notification INTEGER,is_podcast INTEGER,album_artist TEXT," +
+                    "duration INTEGER,bookmark INTEGER,artist TEXT,album TEXT,resolution TEXT," +
+                    "tags TEXT,category TEXT,language TEXT,mini_thumb_data TEXT,name TEXT," +
+                    "media_type INTEGER,old_id INTEGER,storage_id INTEGER,is_drm INTEGER," +
+                    "width INTEGER, height INTEGER);");
+
+            // copy data from old table, squashing entries with duplicate _data
+            db.execSQL("INSERT OR REPLACE INTO files2 SELECT * FROM files;");
+            db.execSQL("DROP TABLE files;");
+            db.execSQL("ALTER TABLE files2 RENAME TO files;");
+
+            // recreate indices and triggers
+            db.execSQL("CREATE INDEX album_id_idx ON files(album_id);");
+            db.execSQL("CREATE INDEX artist_id_idx ON files(artist_id);");
+            db.execSQL("CREATE INDEX bucket_index on files(bucket_id,media_type," +
+                    "datetaken, _id);");
+            db.execSQL("CREATE INDEX bucket_name on files(bucket_id,media_type," +
+                    "bucket_display_name);");
+            db.execSQL("CREATE INDEX format_index ON files(format);");
+            db.execSQL("CREATE INDEX media_type_index ON files(media_type);");
+            db.execSQL("CREATE INDEX parent_index ON files(parent);");
+            db.execSQL("CREATE INDEX path_index ON files(_data);");
+            db.execSQL("CREATE INDEX sort_index ON files(datetaken ASC, _id ASC);");
+            db.execSQL("CREATE INDEX title_idx ON files(title);");
+            db.execSQL("CREATE INDEX titlekey_index ON files(title_key);");
+            if (!internal) {
+                db.execSQL("CREATE TRIGGER audio_playlists_cleanup DELETE ON files" +
+                        " WHEN old.media_type=4" +
+                        " BEGIN DELETE FROM audio_playlists_map WHERE playlist_id = old._id;" +
+                        "SELECT _DELETE_FILE(old._data);END;");
+                db.execSQL("CREATE TRIGGER files_cleanup DELETE ON files" +
+                        " BEGIN SELECT _OBJECT_REMOVED(old._id);END;");
+            }
+        }
+
+        if (fromVersion < 601) {
             // remove primary key constraint because column time is not necessarily unique
             db.execSQL("CREATE TABLE IF NOT EXISTS log_tmp (time DATETIME, message TEXT);");
             db.execSQL("DELETE FROM log_tmp;");
-            db.execSQL("INSERT INTO log_tmp SELECT time, message FROM log;");
+            db.execSQL("INSERT INTO log_tmp SELECT time, message FROM log order by rowid;");
             db.execSQL("DROP TABLE log;");
             db.execSQL("ALTER TABLE log_tmp RENAME TO log;");
         }
@@ -1769,7 +1826,8 @@
      * Write a persistent diagnostic message to the log table.
      */
     static void logToDb(SQLiteDatabase db, String message) {
-        db.execSQL("INSERT INTO log (time,message) VALUES (strftime('%Y-%m-%d %H:%M:%f','now'),?);",
+        db.execSQL("INSERT OR REPLACE" +
+                " INTO log (time,message) VALUES (strftime('%Y-%m-%d %H:%M:%f','now'),?);",
                 new String[] { message });
         // delete all but the last 500 rows
         db.execSQL("DELETE FROM log WHERE rowid IN" +
@@ -2448,7 +2506,10 @@
                 combine(prependArgs, selectionArgs), groupBy, null, sort, limit);
 
         if (c != null) {
-            c.setNotificationUri(getContext().getContentResolver(), uri);
+            String nonotify = uri.getQueryParameter("nonotify");
+            if (nonotify == null || !nonotify.equals("1")) {
+                c.setNotificationUri(getContext().getContentResolver(), uri);
+            }
         }
 
         return c;
@@ -2574,7 +2635,7 @@
 
     /**
      * Ensures there is a file in the _data column of values, if one isn't
-     * present a new file is created.
+     * present a new filename is generated. The file itself is not created.
      *
      * @param initialValues the values passed to insert by the caller
      * @return the new values
@@ -2591,9 +2652,7 @@
             values = initialValues;
         }
 
-        if (!ensureFileExists(file)) {
-            throw new IllegalStateException("Unable to create new file: " + file);
-        }
+        // we used to create the file here, but now defer this until openFile() is called
         return values;
     }
 
@@ -2762,11 +2821,7 @@
                 return cid;
             }
 
-            String selection = (mCaseInsensitivePaths ? MediaStore.MediaColumns.DATA
-                    + " =?1 COLLATE nocase"
-                    // search only directories.
-                    + " AND format=" + MtpConstants.FORMAT_ASSOCIATION
-                    : MediaStore.MediaColumns.DATA + "=?");
+            String selection = MediaStore.MediaColumns.DATA + "=?";
             String [] selargs = { parentPath };
             helper.mNumQueries++;
             Cursor c = db.query("files", sIdOnlyColumn, selection, selargs, null, null, null);
@@ -3002,7 +3057,9 @@
                 File file = new File(path);
                 if (file.exists()) {
                     values.put(FileColumns.DATE_MODIFIED, file.lastModified() / 1000);
-                    values.put(FileColumns.SIZE, file.length());
+                    if (!values.containsKey(FileColumns.SIZE)) {
+                        values.put(FileColumns.SIZE, file.length());
+                    }
                     // make sure date taken time is set
                     if (mediaType == FileColumns.MEDIA_TYPE_IMAGE
                             || mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
@@ -3459,7 +3516,7 @@
         ContentValues mediatype = new ContentValues();
         mediatype.put("media_type", 0);
         int numrows = db.update("files", mediatype,
-                "_data >= ? COLLATE nocase AND _data < ? COLLATE nocase",
+                "_data >= ? AND _data < ?",
                 new String[] { hiddenroot  + "/", hiddenroot + "0"});
         helper.mNumUpdates += numrows;
         ContentResolver res = getContext().getContentResolver();
@@ -3498,7 +3555,7 @@
         @Override
         public void onMediaScannerConnected() {
             Cursor c = mDb.query("files", openFileColumns,
-                    "_data >= ? COLLATE nocase AND _data < ? COLLATE nocase",
+                    "_data >= ? AND _data < ?",
                     new String[] { mPath + "/", mPath + "0"},
                     null, null, null);
             while (c.moveToNext()) {
@@ -3993,7 +4050,7 @@
                                     // also update bucket_display_name
                                     ",bucket_display_name=?6" +
                                     ",bucket_id=?7" +
-                                    " WHERE _data >= ?3 COLLATE nocase AND _data < ?4 COLLATE nocase;",
+                                    " WHERE _data >= ?3 AND _data < ?4;",
                                     bindArgs);
                         }
 
@@ -4398,14 +4455,10 @@
                         WRITE_EXTERNAL_STORAGE, "External path: " + path);
             }
 
-            // bypass emulation layer when file is opened for reading, but only
+            // Bypass emulation layer when file is opened for reading, but only
             // when opening read-only and we have an exact match.
-            if (modeBits == MODE_READ_ONLY && Environment.isExternalStorageEmulated()) {
-                final File directFile = new File(Environment.getMediaStorageDirectory(), path
-                        .substring(sExternalPath.length()));
-                if (directFile.exists()) {
-                    file = directFile;
-                }
+            if (modeBits == MODE_READ_ONLY) {
+                file = Environment.maybeTranslateEmulatedPathToInternal(file);
             }
 
         } else if (path.startsWith(sCachePath)) {
@@ -4597,6 +4650,9 @@
                 long rowId = db.insert("album_art", MediaStore.MediaColumns.DATA, values);
                 if (rowId > 0) {
                     out = ContentUris.withAppendedId(ALBUMART_URI, rowId);
+                    // ensure the parent directory exists
+                    String albumart_path = values.getAsString(MediaStore.MediaColumns.DATA);
+                    ensureFileExists(albumart_path);
                 }
             } catch (IllegalStateException ex) {
                 Log.e(TAG, "error creating album thumb file");
@@ -4608,29 +4664,22 @@
     // Write out the album art to the output URI, recompresses the given Bitmap
     // if necessary, otherwise writes the compressed data.
     private void writeAlbumArt(
-            boolean need_to_recompress, Uri out, byte[] compressed, Bitmap bm) {
-        boolean success = false;
+            boolean need_to_recompress, Uri out, byte[] compressed, Bitmap bm) throws IOException {
+        OutputStream outstream = null;
         try {
-            OutputStream outstream = getContext().getContentResolver().openOutputStream(out);
+            outstream = getContext().getContentResolver().openOutputStream(out);
 
             if (!need_to_recompress) {
                 // No need to recompress here, just write out the original
                 // compressed data here.
                 outstream.write(compressed);
-                success = true;
             } else {
-                success = bm.compress(Bitmap.CompressFormat.JPEG, 85, outstream);
+                if (!bm.compress(Bitmap.CompressFormat.JPEG, 85, outstream)) {
+                    throw new IOException("failed to compress bitmap");
+                }
             }
-
-            outstream.close();
-        } catch (FileNotFoundException ex) {
-            Log.e(TAG, "error creating file", ex);
-        } catch (IOException ex) {
-            Log.e(TAG, "error creating file", ex);
-        }
-        if (!success) {
-            // the thumbnail was not written successfully, delete the entry that refers to it
-            getContext().getContentResolver().delete(out, null, null);
+        } finally {
+            IoUtils.closeQuietly(outstream);
         }
     }
 
@@ -4709,17 +4758,19 @@
             // that could go wrong while generating the thumbnail, and we only want
             // to update the database when all steps succeeded.
             d.db.beginTransaction();
+            Uri out = null;
+            ParcelFileDescriptor pfd = null;
             try {
-                Uri out = getAlbumArtOutputUri(d.helper, d.db, d.album_id, d.albumart_uri);
+                out = getAlbumArtOutputUri(d.helper, d.db, d.album_id, d.albumart_uri);
 
                 if (out != null) {
                     writeAlbumArt(need_to_recompress, out, compressed, bm);
                     getContext().getContentResolver().notifyChange(MEDIA_URI, null);
-                    ParcelFileDescriptor pfd = openFileHelper(out, "r");
+                    pfd = openFileHelper(out, "r");
                     d.db.setTransactionSuccessful();
                     return pfd;
                 }
-            } catch (FileNotFoundException ex) {
+            } catch (IOException ex) {
                 // do nothing, just return null below
             } catch (UnsupportedOperationException ex) {
                 // do nothing, just return null below
@@ -4728,6 +4779,13 @@
                 if (bm != null) {
                     bm.recycle();
                 }
+                if (pfd == null && out != null) {
+                    // Thumbnail was not written successfully, delete the entry that refers to it.
+                    // Note that this only does something if getAlbumArtOutputUri() reused an
+                    // existing entry from the database. If a new entry was created, it will
+                    // have been rolled back as part of backing out the transaction.
+                    getContext().getContentResolver().delete(out, null, null);
+                }
             }
         }
         return null;
diff --git a/src/com/android/providers/media/MtpService.java b/src/com/android/providers/media/MtpService.java
index 04033e9..74fd747 100644
--- a/src/com/android/providers/media/MtpService.java
+++ b/src/com/android/providers/media/MtpService.java
@@ -71,13 +71,23 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                synchronized (mBinder) {
-                    // Unhide the storage units when the user has unlocked the lockscreen
-                    if (mMtpDisabled) {
-                        addStorageDevicesLocked();
-                        mMtpDisabled = false;
-                    }
-                }
+                // If the media scanner is running, it may currently be calling
+                // sendObjectAdded/Removed, which also synchronizes on mBinder
+                // (and in addition to that, all the native MtpServer methods
+                // lock the same Mutex). If it happens to be in an mtp device
+                // write(), it may block for some time, so process this broadcast
+                // in a thread.
+                new Thread(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mBinder) {
+                            // Unhide the storage units when the user has unlocked the lockscreen
+                            if (mMtpDisabled) {
+                                addStorageDevicesLocked();
+                                mMtpDisabled = false;
+                            }
+                        }
+                    }}, "addStorageDevices").start();
             }
         }
     };
diff --git a/tools/genfiles/genfiles.sh b/tools/genfiles/genfiles.sh
index 32d2352..2a139a5 100755
--- a/tools/genfiles/genfiles.sh
+++ b/tools/genfiles/genfiles.sh
@@ -139,7 +139,7 @@
 }
 
 echo mkfiles.sh generated. Now run:
-grep sdcard0\/proto mkfiles.sh |sed 's/cat \/storage\/sdcard0\//adb push /' | sed 's/ > .*/ \/storage\/sdcard0/'|sort -u
+grep sdcard0\/proto mkfiles.sh |sed 's/cat \/storage\/sdcard0\//adb push protos\//' | sed 's/ > .*/ \/storage\/sdcard0\//'|sort -u
 echo adb push mkfiles.sh /storage/sdcard0
 echo adb shell sh /storage/sdcard0/mkfiles.sh