Public APIs for ID-specific Uris, misc items.

We have getContentUri() for entire collections of items, but we
only have ID-specific overloads for some of the MediaStore classes;
let's get them all added for consistency.

Remove primary/secondary directory logic, which was replaced by
new RELATIVE_PATH column before Q launched.

Bug: 137890034
Test: atest --test-mapping packages/providers/MediaProvider
Exempt-From-Owner-Approval: trivial API refactoring
Change-Id: Iae4e7fe57adff071c35af459e31223a1fd05fef2
diff --git a/api/current.txt b/api/current.txt
index c68120d..8a6d2bf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9348,6 +9348,7 @@
     field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
     field public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
     field public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
+    field public static final String MIMETYPE_UNKNOWN = "application/octet-stream";
   }
 
   public class ClipboardManager extends android.text.ClipboardManager {
@@ -9695,6 +9696,7 @@
     method public Long getAsLong(String);
     method public Short getAsShort(String);
     method public String getAsString(String);
+    method public boolean isEmpty();
     method public java.util.Set<java.lang.String> keySet();
     method public void put(String, String);
     method public void put(String, Byte);
@@ -34530,6 +34532,7 @@
     method public static String getExternalStorageState();
     method public static String getExternalStorageState(java.io.File);
     method @NonNull public static java.io.File getRootDirectory();
+    method @NonNull public static java.io.File getStorageDirectory();
     method @Deprecated public static String getStorageState(java.io.File);
     method public static boolean isExternalStorageEmulated();
     method public static boolean isExternalStorageEmulated(@NonNull java.io.File);
@@ -38353,8 +38356,10 @@
     ctor public MediaStore();
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
+    method public static boolean getIncludePending(@NonNull android.net.Uri);
     method public static android.net.Uri getMediaScannerUri();
     method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method public static boolean getRequireOriginal(@NonNull android.net.Uri);
     method @NonNull public static String getVersion(@NonNull android.content.Context);
     method @NonNull public static String getVersion(@NonNull android.content.Context, @NonNull String);
     method @NonNull public static String getVolumeName(@NonNull android.net.Uri);
@@ -38497,6 +38502,7 @@
   public static final class MediaStore.Audio.Media implements android.provider.MediaStore.Audio.AudioColumns {
     ctor public MediaStore.Audio.Media();
     method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
     method @Deprecated @Nullable public static android.net.Uri getContentUriForPath(@NonNull String);
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
     field public static final String DEFAULT_SORT_ORDER = "title_key";
@@ -38547,6 +38553,7 @@
 
   public static final class MediaStore.Downloads implements android.provider.MediaStore.DownloadColumns {
     method @NonNull public static android.net.Uri getContentUri(@NonNull String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/download";
     field @NonNull public static final android.net.Uri EXTERNAL_CONTENT_URI;
     field @NonNull public static final android.net.Uri INTERNAL_CONTENT_URI;
@@ -38587,6 +38594,7 @@
     ctor public MediaStore.Images.Media();
     method @Deprecated public static android.graphics.Bitmap getBitmap(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException, java.io.IOException;
     method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
     method @Deprecated public static String insertImage(android.content.ContentResolver, String, String, String) throws java.io.FileNotFoundException;
     method @Deprecated public static String insertImage(android.content.ContentResolver, android.graphics.Bitmap, String, String);
     method @Deprecated public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, String[]);
@@ -38656,6 +38664,7 @@
   public static final class MediaStore.Video.Media implements android.provider.MediaStore.Video.VideoColumns {
     ctor public MediaStore.Video.Media();
     method public static android.net.Uri getContentUri(String);
+    method @NonNull public static android.net.Uri getContentUri(@NonNull String, long);
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
     field public static final String DEFAULT_SORT_ORDER = "title";
     field public static final android.net.Uri EXTERNAL_CONTENT_URI;
diff --git a/api/removed.txt b/api/removed.txt
index 74c1d3c..db784a8 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -481,9 +481,8 @@
   @Deprecated public static class MediaStore.PendingParams {
     ctor public MediaStore.PendingParams(@NonNull android.net.Uri, @NonNull String, @NonNull String);
     method public void setDownloadUri(@Nullable android.net.Uri);
-    method public void setPrimaryDirectory(@Nullable String);
     method public void setRefererUri(@Nullable android.net.Uri);
-    method public void setSecondaryDirectory(@Nullable String);
+    method public void setRelativePath(@Nullable String);
   }
 
   @Deprecated public static class MediaStore.PendingSession implements java.lang.AutoCloseable {
diff --git a/api/test-current.txt b/api/test-current.txt
index ec3cb06..4eda495 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1736,7 +1736,6 @@
   public class Environment {
     method public static java.io.File buildPath(java.io.File, java.lang.String...);
     method @NonNull public static java.io.File getProductDirectory();
-    method @NonNull public static java.io.File getStorageDirectory();
   }
 
   public final class FileUtils {
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 29acd5d..0c6a935 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -64,6 +64,16 @@
     public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
 
     /**
+     * The MIME type for data whose type is otherwise unknown.
+     * <p>
+     * Per RFC 2046, the "application" media type is to be used for discrete
+     * data which do not fit in any of the other categories, and the
+     * "octet-stream" subtype is used to indicate that a body contains arbitrary
+     * binary data.
+     */
+    public static final String MIMETYPE_UNKNOWN = "application/octet-stream";
+
+    /**
      * The name of the extra used to define a component name when copying/dragging
      * an app icon from Launcher.
      * <p>
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 9c86359..1f48393 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -475,11 +475,9 @@
      */
     public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
 
-    /**
-     * Default MIME type for files whose type is otherwise unknown.
-     * @hide
-     */
-    public static final String MIME_TYPE_DEFAULT = "application/octet-stream";
+    /** {@hide} */
+    @Deprecated
+    public static final String MIME_TYPE_DEFAULT = ClipDescription.MIMETYPE_UNKNOWN;
 
     /** @hide */
     @UnsupportedAppUsage
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 8223a0b..bdd1f4c 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -259,8 +259,6 @@
      * Indicates whether this collection is empty.
      *
      * @return true iff size == 0
-     * {@hide}
-     * TODO: consider exposing this new method publicly
      */
     public boolean isEmpty() {
         return mMap.isEmpty();
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 3462d1f..6d5fe53b 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -168,8 +168,11 @@
         return DIR_ANDROID_ROOT;
     }
 
-    /** {@hide} */
-    @TestApi
+    /**
+     * Return root directory where all external storage devices will be mounted.
+     * For example, {@link #getExternalStorageDirectory()} will appear under
+     * this location.
+     */
     public static @NonNull File getStorageDirectory() {
         return DIR_ANDROID_STORAGE;
     }
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index a959913f..079a42d 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -553,7 +553,7 @@
      * By default no pending items are returned.
      *
      * @see MediaColumns#IS_PENDING
-     * @see MediaStore#setIncludePending(Uri)
+     * @see MediaStore#getIncludePending(Uri)
      */
     public static @NonNull Uri setIncludePending(@NonNull Uri uri) {
         return setIncludePending(uri.buildUpon()).build();
@@ -565,6 +565,17 @@
     }
 
     /**
+     * Return if any pending media items should be included in calls such as
+     * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
+     *
+     * @see MediaColumns#IS_PENDING
+     * @see MediaStore#setIncludePending(Uri)
+     */
+    public static boolean getIncludePending(@NonNull Uri uri) {
+        return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_INCLUDE_PENDING));
+    }
+
+    /**
      * Update the given {@link Uri} to also include any trashed media items from
      * calls such as
      * {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
@@ -594,12 +605,24 @@
      * {@link UnsupportedOperationException} will be thrown when the returned
      * {@link Uri} is used, such as when the caller doesn't hold
      * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}.
+     *
+     * @see MediaStore#getRequireOriginal(Uri)
      */
     public static @NonNull Uri setRequireOriginal(@NonNull Uri uri) {
         return uri.buildUpon().appendQueryParameter(PARAM_REQUIRE_ORIGINAL, "1").build();
     }
 
     /**
+     * Return if the caller requires the original file contents when calling
+     * {@link ContentResolver#openFileDescriptor(Uri, String)}.
+     *
+     * @see MediaStore#setRequireOriginal(Uri)
+     */
+    public static boolean getRequireOriginal(@NonNull Uri uri) {
+        return parseBoolean(uri.getQueryParameter(MediaStore.PARAM_REQUIRE_ORIGINAL));
+    }
+
+    /**
      * Create a new pending media item using the given parameters. Pending items
      * are expected to have a short lifetime, and owners should either
      * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
@@ -666,45 +689,11 @@
                     (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
         }
 
-        /**
-         * Optionally set the primary directory under which this pending item
-         * should be persisted. Only specific well-defined directories from
-         * {@link Environment} are allowed based on the media type being
-         * inserted.
-         * <p>
-         * For example, when creating pending {@link MediaStore.Images.Media}
-         * items, only {@link Environment#DIRECTORY_PICTURES} or
-         * {@link Environment#DIRECTORY_DCIM} are allowed.
-         * <p>
-         * You may leave this value undefined to store the media in a default
-         * location. For example, when this value is left undefined, pending
-         * {@link MediaStore.Audio.Media} items are stored under
-         * {@link Environment#DIRECTORY_MUSIC}.
-         *
-         * @see MediaColumns#PRIMARY_DIRECTORY
-         */
-        public void setPrimaryDirectory(@Nullable String primaryDirectory) {
-            if (primaryDirectory == null) {
-                this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY);
+        public void setRelativePath(@Nullable String relativePath) {
+            if (relativePath == null) {
+                this.insertValues.remove(MediaColumns.RELATIVE_PATH);
             } else {
-                this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory);
-            }
-        }
-
-        /**
-         * Optionally set the secondary directory under which this pending item
-         * should be persisted. Any valid directory name is allowed.
-         * <p>
-         * You may leave this value undefined to store the media as a direct
-         * descendant of the {@link #setPrimaryDirectory(String)} location.
-         *
-         * @see MediaColumns#SECONDARY_DIRECTORY
-         */
-        public void setSecondaryDirectory(@Nullable String secondaryDirectory) {
-            if (secondaryDirectory == null) {
-                this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY);
-            } else {
-                this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory);
+                this.insertValues.put(MediaColumns.RELATIVE_PATH, relativePath);
             }
         }
 
@@ -1470,7 +1459,14 @@
                     .appendPath("downloads").build();
         }
 
-        /** @hide */
+        /**
+         * Get the content:// style URI for a single row in the downloads table
+         * on the given volume.
+         *
+         * @param volumeName the name of the volume to get the URI for
+         * @param id the download to get the URI for
+         * @return the URI to the downloads table on the given volume
+         */
         public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
             return ContentUris.withAppendedId(getContentUri(volumeName), id);
         }
@@ -1795,7 +1791,14 @@
                         .appendPath("media").build();
             }
 
-            /** @hide */
+            /**
+             * Get the content:// style URI for a single row in the images table
+             * on the given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @param id the image to get the URI for
+             * @return the URI to the images table on the given volume
+             */
             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
             }
@@ -2282,7 +2285,14 @@
                         .appendPath("media").build();
             }
 
-            /** @hide */
+            /**
+             * Get the content:// style URI for a single row in the audio table
+             * on the given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @param id the audio to get the URI for
+             * @return the URI to the audio table on the given volume
+             */
             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
             }
@@ -3031,7 +3041,14 @@
                         .appendPath("media").build();
             }
 
-            /** @hide */
+            /**
+             * Get the content:// style URI for a single row in the videos table
+             * on the given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @param id the video to get the URI for
+             * @return the URI to the videos table on the given volume
+             */
             public static @NonNull Uri getContentUri(@NonNull String volumeName, long id) {
                 return ContentUris.withAppendedId(getContentUri(volumeName), id);
             }
@@ -3296,6 +3313,13 @@
         return volumeName;
     }
 
+    private static boolean parseBoolean(@Nullable String value) {
+        if (value == null) return false;
+        if ("1".equals(value)) return true;
+        if ("true".equalsIgnoreCase(value)) return true;
+        return false;
+    }
+
     /**
      * Return path where the given specific volume is mounted. Not valid for
      * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index d47288a..8670d1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -87,6 +87,7 @@
 
 import libcore.io.IoUtils;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.text.DateFormat;
@@ -254,8 +255,8 @@
             // Save the screenshot to the MediaStore
             final MediaStore.PendingParams params = new MediaStore.PendingParams(
                     MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mImageFileName, "image/png");
-            params.setPrimaryDirectory(Environment.DIRECTORY_PICTURES);
-            params.setSecondaryDirectory(Environment.DIRECTORY_SCREENSHOTS);
+            params.setRelativePath(Environment.DIRECTORY_PICTURES + File.separator
+                    + Environment.DIRECTORY_SCREENSHOTS);
 
             final Uri uri = MediaStore.createPending(context, params);
             final MediaStore.PendingSession session = MediaStore.openPending(context, uri);