Utilities for content:// Uris and file modes.

Bug: 111268862, 111960973
Test: atest cts/tests/tests/content/src/android/content/cts/ContentUrisTest.java
Test: atest frameworks/base/core/tests/coretests/src/android/os/FileUtilsTest.java
Change-Id: I94373055468d279e6553d4a038267732b9b53745
diff --git a/api/current.txt b/api/current.txt
index f8825d9..67ad896 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -9332,6 +9332,7 @@
     ctor public ContentUris();
     method public static android.net.Uri.Builder appendId(android.net.Uri.Builder, long);
     method public static long parseId(android.net.Uri);
+    method public static android.net.Uri removeId(android.net.Uri);
     method public static android.net.Uri withAppendedId(android.net.Uri, long);
   }
 
diff --git a/core/java/android/content/ContentUris.java b/core/java/android/content/ContentUris.java
index dbe8a7c..fd7b372 100644
--- a/core/java/android/content/ContentUris.java
+++ b/core/java/android/content/ContentUris.java
@@ -18,6 +18,8 @@
 
 import android.net.Uri;
 
+import java.util.List;
+
 /**
 * Utility methods useful for working with {@link android.net.Uri} objects
 * that use the "content" (content://) scheme.
@@ -109,4 +111,30 @@
     public static Uri withAppendedId(Uri contentUri, long id) {
         return appendId(contentUri.buildUpon(), id).build();
     }
+
+    /**
+     * Removes any ID from the end of the path.
+     *
+     * @param contentUri that ends with an ID
+     * @return a new URI with the ID removed from the end of the path
+     * @throws IllegalArgumentException when the given URI has no ID to remove
+     *             from the end of the path
+     */
+    public static Uri removeId(Uri contentUri) {
+        // Verify that we have a valid ID to actually remove
+        final String last = contentUri.getLastPathSegment();
+        if (last == null) {
+            throw new IllegalArgumentException("No path segments to remove");
+        } else {
+            Long.parseLong(last);
+        }
+
+        final List<String> segments = contentUri.getPathSegments();
+        final Uri.Builder builder = contentUri.buildUpon();
+        builder.path(null);
+        for (int i = 0; i < segments.size() - 1; i++) {
+            builder.appendPath(segments.get(i));
+        }
+        return builder.build();
+    }
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 1c8029c..a9cb0d9 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,6 +16,18 @@
 
 package android.os;
 
+import static android.os.ParcelFileDescriptor.MODE_APPEND;
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
 import static android.system.OsConstants.SPLICE_F_MORE;
 import static android.system.OsConstants.SPLICE_F_MOVE;
 import static android.system.OsConstants.S_ISFIFO;
@@ -1061,8 +1073,13 @@
                 mimeTypeFromExt = ContentResolver.MIME_TYPE_DEFAULT;
             }
 
-            final String extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(
-                    mimeType);
+            final String extFromMimeType;
+            if (ContentResolver.MIME_TYPE_DEFAULT.equals(mimeType)) {
+                extFromMimeType = null;
+            } else {
+                extFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
+            }
+
             if (Objects.equals(mimeType, mimeTypeFromExt) || Objects.equals(ext, extFromMimeType)) {
                 // Extension maps back to requested MIME type; allow it
             } else {
@@ -1180,6 +1197,96 @@
     }
 
     /** {@hide} */
+    public static int translateModeStringToPosix(String mode) {
+        int res = 0;
+        if (mode.startsWith("rw")) {
+            res |= O_RDWR | O_CREAT;
+        } else if (mode.startsWith("w")) {
+            res |= O_WRONLY | O_CREAT;
+        } else if (mode.startsWith("r")) {
+            res |= O_RDONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if (mode.indexOf('t') != -1) {
+            res |= O_TRUNC;
+        }
+        if (mode.indexOf('a') != -1) {
+            res |= O_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static String translateModePosixToString(int mode) {
+        String res = "";
+        if ((mode & O_RDWR) == O_RDWR) {
+            res += "rw";
+        } else if ((mode & O_WRONLY) == O_WRONLY) {
+            res += "w";
+        } else if ((mode & O_RDONLY) == O_RDONLY) {
+            res += "r";
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & O_TRUNC) == O_TRUNC) {
+            res += "t";
+        }
+        if ((mode & O_APPEND) == O_APPEND) {
+            res += "a";
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static int translateModePosixToPfd(int mode) {
+        int res = 0;
+        if ((mode & O_RDWR) == O_RDWR) {
+            res |= MODE_READ_WRITE;
+        } else if ((mode & O_WRONLY) == O_WRONLY) {
+            res |= MODE_WRITE_ONLY;
+        } else if ((mode & O_RDONLY) == O_RDONLY) {
+            res |= MODE_READ_ONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & O_CREAT) == O_CREAT) {
+            res |= MODE_CREATE;
+        }
+        if ((mode & O_TRUNC) == O_TRUNC) {
+            res |= MODE_TRUNCATE;
+        }
+        if ((mode & O_APPEND) == O_APPEND) {
+            res |= MODE_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
+    public static int translateModePfdToPosix(int mode) {
+        int res = 0;
+        if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
+            res |= O_RDWR;
+        } else if ((mode & MODE_WRITE_ONLY) == MODE_WRITE_ONLY) {
+            res |= O_WRONLY;
+        } else if ((mode & MODE_READ_ONLY) == MODE_READ_ONLY) {
+            res |= O_RDONLY;
+        } else {
+            throw new IllegalArgumentException("Bad mode: " + mode);
+        }
+        if ((mode & MODE_CREATE) == MODE_CREATE) {
+            res |= O_CREAT;
+        }
+        if ((mode & MODE_TRUNCATE) == MODE_TRUNCATE) {
+            res |= O_TRUNC;
+        }
+        if ((mode & MODE_APPEND) == MODE_APPEND) {
+            res |= O_APPEND;
+        }
+        return res;
+    }
+
+    /** {@hide} */
     @VisibleForTesting
     public static class MemoryPipe extends Thread implements AutoCloseable {
         private final FileDescriptor[] pipe;
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index c9edc53..a54c589 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -559,28 +559,7 @@
      * @throws IllegalArgumentException if the given string does not match a known file mode.
      */
     public static int parseMode(String mode) {
-        final int modeBits;
-        if ("r".equals(mode)) {
-            modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
-        } else if ("w".equals(mode) || "wt".equals(mode)) {
-            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
-                    | ParcelFileDescriptor.MODE_CREATE
-                    | ParcelFileDescriptor.MODE_TRUNCATE;
-        } else if ("wa".equals(mode)) {
-            modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
-                    | ParcelFileDescriptor.MODE_CREATE
-                    | ParcelFileDescriptor.MODE_APPEND;
-        } else if ("rw".equals(mode)) {
-            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
-                    | ParcelFileDescriptor.MODE_CREATE;
-        } else if ("rwt".equals(mode)) {
-            modeBits = ParcelFileDescriptor.MODE_READ_WRITE
-                    | ParcelFileDescriptor.MODE_CREATE
-                    | ParcelFileDescriptor.MODE_TRUNCATE;
-        } else {
-            throw new IllegalArgumentException("Bad mode '" + mode + "'");
-        }
-        return modeBits;
+        return FileUtils.translateModePosixToPfd(FileUtils.translateModeStringToPosix(mode));
     }
 
     /**
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 9c9f11b..20fe162 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -17,6 +17,22 @@
 package android.os;
 
 import static android.os.FileUtils.roundStorageSize;
+import static android.os.FileUtils.translateModePfdToPosix;
+import static android.os.FileUtils.translateModePosixToPfd;
+import static android.os.FileUtils.translateModePosixToString;
+import static android.os.FileUtils.translateModeStringToPosix;
+import static android.os.ParcelFileDescriptor.MODE_APPEND;
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
+import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
+import static android.system.OsConstants.O_APPEND;
+import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
+import static android.system.OsConstants.O_RDWR;
+import static android.system.OsConstants.O_TRUNC;
+import static android.system.OsConstants.O_WRONLY;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
@@ -476,6 +492,32 @@
         assertEquals(G64, roundStorageSize(G32 + 1));
     }
 
+    @Test
+    public void testTranslateMode() throws Exception {
+        assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
+
+        assertTranslate("rw", O_RDWR | O_CREAT,
+                MODE_READ_WRITE | MODE_CREATE);
+        assertTranslate("rwt", O_RDWR | O_CREAT | O_TRUNC,
+                MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);
+        assertTranslate("rwa", O_RDWR | O_CREAT | O_APPEND,
+                MODE_READ_WRITE | MODE_CREATE | MODE_APPEND);
+
+        assertTranslate("w", O_WRONLY | O_CREAT,
+                MODE_WRITE_ONLY | MODE_CREATE | MODE_CREATE);
+        assertTranslate("wt", O_WRONLY | O_CREAT | O_TRUNC,
+                MODE_WRITE_ONLY | MODE_CREATE | MODE_TRUNCATE);
+        assertTranslate("wa", O_WRONLY | O_CREAT | O_APPEND,
+                MODE_WRITE_ONLY | MODE_CREATE | MODE_APPEND);
+    }
+
+    private static void assertTranslate(String string, int posix, int pfd) {
+        assertEquals(posix, translateModeStringToPosix(string));
+        assertEquals(string, translateModePosixToString(posix));
+        assertEquals(pfd, translateModePosixToPfd(posix));
+        assertEquals(posix, translateModePfdToPosix(pfd));
+    }
+
     private static void assertNameEquals(String expected, File actual) {
         assertEquals(expected, actual.getName());
     }