Merge "Cache binder LocalCallingIdentity across requests" into rvc-dev
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index ef6c27f..2a3f792 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -83,6 +83,8 @@
      *
      * Note: {@code FORCE_ENABLE_SCOPED_STORAGE} should also be disabled for apps to opt out of
      * scoped storage.
+     * Note: This flag is also used in android.os.Environment. Any modifications to this flag
+     * should be reflected there as well.
      * See https://developer.android.com/training/data-storage#scoped-storage for more information.
      */
     @ChangeId
@@ -98,6 +100,8 @@
      *
      * Note: {@code DEFAULT_SCOPED_STORAGE} should also be enabled for apps to be enforced into
      * scoped storage.
+     * Note: This flag is also used in android.os.Environment. Any modifications to this flag
+     * should be reflected there as well.
      * See https://developer.android.com/training/data-storage#scoped-storage for more information.
      */
     @ChangeId
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index d4a83b6..eb3dd26 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -64,7 +64,10 @@
 import static com.android.providers.media.util.FileUtils.extractRelativePathForDirectory;
 import static com.android.providers.media.util.FileUtils.extractTopLevelDir;
 import static com.android.providers.media.util.FileUtils.extractVolumeName;
+import static com.android.providers.media.util.FileUtils.getAbsoluteSanitizedPath;
 import static com.android.providers.media.util.FileUtils.isDownload;
+import static com.android.providers.media.util.FileUtils.sanitizeDisplayName;
+import static com.android.providers.media.util.FileUtils.sanitizePath;
 import static com.android.providers.media.util.Logging.LOGV;
 import static com.android.providers.media.util.Logging.TAG;
 import static com.android.providers.media.util.PermissionUtils.checkPermissionManageExternalStorage;
@@ -2312,34 +2315,6 @@
         Trace.endSection();
     }
 
-    private static @NonNull String[] sanitizePath(@Nullable String path) {
-        if (path == null) {
-            return new String[0];
-        } else {
-            final String[] segments = path.split("/");
-            // If the path corresponds to the top level directory, then we return an empty path
-            // which denotes the top level directory
-            if (segments.length == 0) {
-                return new String[] { "" };
-            }
-            for (int i = 0; i < segments.length; i++) {
-                segments[i] = sanitizeDisplayName(segments[i]);
-            }
-            return segments;
-        }
-    }
-
-    private static @Nullable String sanitizeDisplayName(@Nullable String name) {
-        if (name == null) {
-            return null;
-        } else if (name.startsWith(".")) {
-            // The resulting file must not be hidden.
-            return FileUtils.buildValidFatFilename("_" + name);
-        } else {
-            return FileUtils.buildValidFatFilename(name);
-        }
-    }
-
     /**
      * Sanity check that any requested {@link MediaColumns#DATA} paths actually
      * live on the storage volume being targeted.
@@ -5437,16 +5412,6 @@
         }
     }
 
-    @Nullable
-    private String getAbsoluteSanitizedPath(String path) {
-        final String[] pathSegments = sanitizePath(path);
-        if (pathSegments.length == 0) {
-            return null;
-        }
-        return path = "/" + String.join("/",
-                Arrays.copyOfRange(pathSegments, 1, pathSegments.length));
-    }
-
     /**
      * Calculates the ranges that need to be redacted for the given file and user that wants to
      * access the file.
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 81a6a34..955c027 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -384,7 +384,8 @@
             final Bundle queryArgs = new Bundle();
             queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
                     formatClause + " AND " + dataClause + " AND " + generationClause);
-            final String pathEscapedForLike = DatabaseUtils.escapeForLike(mRoot.getAbsolutePath());
+            final String pathEscapedForLike = DatabaseUtils.escapeForLike(
+                    FileUtils.getAbsoluteSanitizedPath(mRoot.getAbsolutePath()));
             queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS,
                     new String[] {pathEscapedForLike + "/%", pathEscapedForLike});
             queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
diff --git a/src/com/android/providers/media/util/FileUtils.java b/src/com/android/providers/media/util/FileUtils.java
index b247f2e..c9cf1e2 100644
--- a/src/com/android/providers/media/util/FileUtils.java
+++ b/src/com/android/providers/media/util/FileUtils.java
@@ -701,4 +701,49 @@
             }
         }
     }
+
+    /** {@hide} **/
+    @Nullable
+    public static String getAbsoluteSanitizedPath(String path) {
+        final String[] pathSegments = sanitizePath(path);
+        if (pathSegments.length == 0) {
+            return null;
+        }
+        return path = "/" + String.join("/",
+                Arrays.copyOfRange(pathSegments, 1, pathSegments.length));
+    }
+
+    /** {@hide} */
+    public static @NonNull String[] sanitizePath(@Nullable String path) {
+        if (path == null) {
+            return new String[0];
+        } else {
+            final String[] segments = path.split("/");
+            // If the path corresponds to the top level directory, then we return an empty path
+            // which denotes the top level directory
+            if (segments.length == 0) {
+                return new String[] { "" };
+            }
+            for (int i = 0; i < segments.length; i++) {
+                segments[i] = sanitizeDisplayName(segments[i]);
+            }
+            return segments;
+        }
+    }
+
+    /**
+     * Sanitizes given name by appending '_' to make it non-hidden and mutating the file
+     * name to make it valid for a FAT filesystem.
+     * @hide
+     */
+    public static @Nullable String sanitizeDisplayName(@Nullable String name) {
+        if (name == null) {
+            return null;
+        } else if (name.startsWith(".")) {
+            // The resulting file must not be hidden.
+            return buildValidFatFilename("_" + name);
+        } else {
+            return buildValidFatFilename(name);
+        }
+    }
 }
diff --git a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
index 94c2a74..886acd8 100644
--- a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
+++ b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.UiAutomation;
 import android.content.ContentProviderClient;
@@ -32,6 +33,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.os.storage.StorageManager;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
@@ -59,6 +61,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Verify that we preserve information from the old "legacy" provider from
@@ -74,6 +77,9 @@
     // TODO: expand test to cover secondary storage devices
     private String mVolumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
 
+    private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
+    private static final long POLLING_SLEEP_MILLIS = 100;
+
     private Uri mExternalAudio;
     private Uri mExternalVideo;
     private Uri mExternalImages;
@@ -211,9 +217,7 @@
         // Clear data on the modern provider so that the initial scan recovers
         // metadata from the legacy provider
         executeShellCommand("pm clear " + modernProvider.applicationInfo.packageName, ui);
-
-        // Sleep to give the MediaProvider time to recover from being killed after clearing data
-        Thread.sleep(5000);
+        pollForExternalStorageState();
 
         // And force a scan to confirm upgraded data survives
         MediaStore.waitForIdle(context.getContentResolver());
@@ -243,6 +247,17 @@
         }
     }
 
+    private static void pollForExternalStorageState() throws Exception {
+        for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+            if(Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
+                    .equals(Environment.MEDIA_MOUNTED)) {
+                return;
+            }
+            SystemClock.sleep(POLLING_SLEEP_MILLIS);
+        }
+        fail("Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
+    }
+
     public static String executeShellCommand(String command, UiAutomation uiAutomation)
             throws IOException {
         Log.v(TAG, "$ " + command);