Merge "Fix bug #6522190 MountService should respond to configuration changes ("INTERNAL STORAGE" string should be translated dynamically)" into jb-dev
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 266233a..1e17a97 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -19,7 +19,7 @@
 import static android.Manifest.permission.ACCESS_CACHE_FILESYSTEM;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
 
 import android.app.SearchManager;
@@ -2717,7 +2717,10 @@
             // case insensitive match when looking for parent directory.
             // TODO: investigate whether a "nocase" constraint on the column and
             // using "=" would give the same result faster.
-            String selection = (mCaseInsensitivePaths ? MediaStore.MediaColumns.DATA + " LIKE ?"
+            String selection = (mCaseInsensitivePaths ? MediaStore.MediaColumns.DATA + " LIKE ?1"
+                    // The like above makes it use the index.
+                    // The comparison below makes it correct when the path has wildcard chars
+                    + " AND lower(_data)=lower(?1)"
                     // search only directories.
                     + " AND format=" + MtpConstants.FORMAT_ASSOCIATION
                     : MediaStore.MediaColumns.DATA + "=?");
@@ -2731,6 +2734,9 @@
                     id = insertDirectory(helper, db, parentPath);
                     if (LOCAL_LOGV) Log.v(TAG, "Inserted " + parentPath);
                 } else {
+                    if (c.getCount() > 1) {
+                        Log.e(TAG, "more than one match for " + parentPath);
+                    }
                     c.moveToFirst();
                     id = c.getLong(0);
                     if (LOCAL_LOGV) Log.v(TAG, "Queried " + parentPath);
@@ -3373,21 +3379,43 @@
      *
      * @param path The path to the new .nomedia file or hidden directory
      */
-    private void processNewNoMediaPath(DatabaseHelper helper, SQLiteDatabase db, String path) {
-        File nomedia = new File(path);
+    private void processNewNoMediaPath(final DatabaseHelper helper, final SQLiteDatabase db,
+            final String path) {
+        final File nomedia = new File(path);
         if (nomedia.exists()) {
-            // only do this if the file actually exists, so we don't get tricked
-            // by someone just inserting a .nomedia entry into the database
-            String hiddenroot = nomedia.isDirectory() ? path : nomedia.getParent();
-            ContentValues mediatype = new ContentValues();
-            mediatype.put("media_type", 0);
-            int numrows = db.update("files", mediatype, "_data LIKE ?", new String[] { hiddenroot  + "/%"});
-            helper.mNumUpdates += numrows;
-            ContentResolver res = getContext().getContentResolver();
-            res.notifyChange(Uri.parse("content://media/"), null);
+            hidePath(helper, db, path);
+        } else {
+            // File doesn't exist. Try again in a little while.
+            // XXX there's probably a better way of doing this
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    SystemClock.sleep(2000);
+                    if (nomedia.exists()) {
+                        hidePath(helper, db, path);
+                    } else {
+                        Log.w(TAG, "does not exist: " + path, new Exception());
+                    }
+                }}).start();
         }
     }
 
+    private void hidePath(DatabaseHelper helper, SQLiteDatabase db, String path) {
+        File nomedia = new File(path);
+        String hiddenroot = nomedia.isDirectory() ? path : nomedia.getParent();
+        ContentValues mediatype = new ContentValues();
+        mediatype.put("media_type", 0);
+        int numrows = db.update("files", mediatype,
+                // the "like" test makes use of the index, while the lower() test ensures it
+                // doesn't match entries it shouldn't when the path contains sqlite wildcards
+                "_data LIKE ? AND lower(substr(_data,1,?))=lower(?)",
+                new String[] { hiddenroot  + "/%",
+                    "" + (hiddenroot.length() + 1), hiddenroot + "/"});
+        helper.mNumUpdates += numrows;
+        ContentResolver res = getContext().getContentResolver();
+        res.notifyChange(Uri.parse("content://media/"), null);
+    }
+
     /*
      * Rescan files for missing metadata and set their type accordingly.
      * There is code for detecting the removal of a nomedia file or renaming of
@@ -3419,8 +3447,12 @@
 
         @Override
         public void onMediaScannerConnected() {
-            Cursor c = mDb.query("files", openFileColumns, "_data like ?",
-                    new String[] { mPath + "/%"}, null, null, null);
+            Cursor c = mDb.query("files", openFileColumns,
+                    // the "like" test makes use of the index, while the lower() ensures it
+                    // doesn't match entries it shouldn't when the path contains sqlite wildcards
+                    "_data like ? AND lower(substr(_data,1,?))=lower(?)",
+                    new String[] { mPath + "/%", "" + (mPath.length() + 1), mPath + "/"},
+                    null, null, null);
             while (c.moveToNext()) {
                 String d = c.getString(0);
                 File f = new File(d);
@@ -3900,9 +3932,15 @@
                                 sGetTableAndWhereParam.where, whereArgs);
                         if (count > 0) {
                             // then update the paths of any files and folders contained in the directory.
-                            Object[] bindArgs = new Object[] {oldPath + "/%", oldPath.length() + 1, newPath};
+                            Object[] bindArgs = new Object[] {newPath, oldPath.length() + 1,
+                                    oldPath + "/%", (oldPath.length() + 1), oldPath + "/"};
                             helper.mNumUpdates++;
-                            db.execSQL("UPDATE files SET _data=?3||SUBSTR(_data, ?2) WHERE _data LIKE ?1;", bindArgs);
+                            db.execSQL("UPDATE files SET _data=?1||SUBSTR(_data, ?2)" +
+                                    // the "like" test makes use of the index, while the lower()
+                                    // test ensures it doesn't match entries it shouldn't when the
+                                    // path contains sqlite wildcards
+                                    " WHERE _data LIKE ?3 AND lower(substr(_data,1,?4))=lower(?5);",
+                                    bindArgs);
                         }
 
                         if (count > 0 && !db.inTransaction()) {
@@ -4254,6 +4292,10 @@
     private File queryForDataFile(Uri uri) throws FileNotFoundException {
         final Cursor cursor = query(
                 uri, new String[] { MediaColumns.DATA }, null, null, null);
+        if (cursor == null) {
+            throw new FileNotFoundException("Missing cursor for " + uri);
+        }
+
         try {
             switch (cursor.getCount()) {
                 case 0:
@@ -4281,7 +4323,7 @@
         final int modeBits = ContentResolver.modeToMode(uri, mode);
         final boolean isWrite = (modeBits & MODE_WRITE_ONLY) != 0;
 
-        final File file = queryForDataFile(uri);
+        File file = queryForDataFile(uri);
         final String path;
         try {
             path = file.getCanonicalPath();
@@ -4297,6 +4339,17 @@
                 getContext().enforceCallingOrSelfPermission(
                         WRITE_EXTERNAL_STORAGE, "External path: " + path);
             }
+
+            // 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;
+                }
+            }
+
         } else if (path.startsWith(sCachePath)) {
             getContext().enforceCallingOrSelfPermission(
                     ACCESS_CACHE_FILESYSTEM, "Cache path: " + path);