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());
}