diff options
| -rwxr-xr-x | api/current.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 94 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 38 | ||||
| -rw-r--r-- | core/java/android/os/FileUtils.java | 20 | ||||
| -rw-r--r-- | core/java/android/provider/MediaStore.java | 15 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/content/ContentResolverTest.java | 15 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/os/FileUtilsTest.java | 14 |
8 files changed, 197 insertions, 3 deletions
diff --git a/api/current.txt b/api/current.txt index 15392b8fe29a..c8d842ddcfb3 100755 --- a/api/current.txt +++ b/api/current.txt @@ -36803,10 +36803,12 @@ package android.provider { method public static android.net.Uri getMediaScannerUri(); method public static android.net.Uri getMediaUri(android.content.Context, android.net.Uri); method public static java.lang.String getVersion(android.content.Context); + method public static java.lang.String getVolumeName(android.net.Uri); field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE"; field public static final java.lang.String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; field public static final java.lang.String AUTHORITY = "media"; + field public static final android.net.Uri AUTHORITY_URI; field public static final java.lang.String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit"; field public static final java.lang.String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion"; field public static final java.lang.String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; diff --git a/api/system-current.txt b/api/system-current.txt index 4512fc3b9c2b..fb97643129bd 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -987,8 +987,8 @@ package android.content { field public static final java.lang.String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; - field public static final java.lang.String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS"; + field public static final java.lang.String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; field public static final java.lang.String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED"; field public static final java.lang.String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4756bf40bad3..ee7d00208e2c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -23,6 +23,8 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME; import static android.app.servertransaction.ActivityLifecycleItem.ON_START; import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; +import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS; +import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; import static android.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; @@ -45,6 +47,7 @@ import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.content.IIntentReceiver; @@ -84,6 +87,7 @@ import android.os.Bundle; import android.os.Debug; import android.os.DropBoxManager; import android.os.Environment; +import android.os.FileUtils; import android.os.GraphicsEnvironment; import android.os.Handler; import android.os.HandlerExecutor; @@ -114,6 +118,9 @@ import android.provider.Settings; import android.renderscript.RenderScriptCacheDir; import android.security.NetworkSecurityPolicy; import android.security.net.config.NetworkSecurityConfigProvider; +import android.system.ErrnoException; +import android.system.OsConstants; +import android.system.StructStat; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.DisplayMetrics; @@ -162,13 +169,16 @@ import dalvik.system.VMRuntime; import libcore.io.DropBox; import libcore.io.EventLogger; +import libcore.io.ForwardingOs; import libcore.io.IoUtils; +import libcore.io.Os; import libcore.net.event.NetworkEventDispatcher; import org.apache.harmony.dalvik.ddmc.DdmVmInternal; import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -6749,7 +6759,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private class DropBoxReporter implements DropBox.Reporter { + private static class DropBoxReporter implements DropBox.Reporter { private DropBoxManager dropBox; @@ -6769,7 +6779,84 @@ public final class ActivityThread extends ClientTransactionHandler { private synchronized void ensureInitialized() { if (dropBox == null) { - dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE); + dropBox = currentActivityThread().getApplication() + .getSystemService(DropBoxManager.class); + } + } + } + + private static class AndroidOs extends ForwardingOs { + /** + * Install selective syscall interception. For example, this is used to + * implement special filesystem paths that will be redirected to + * {@link ContentResolver#openFileDescriptor(Uri, String)}. + */ + public static void install() { + // If feature is disabled, we don't need to install + if (!DEPRECATE_DATA_COLUMNS) return; + + // If app is modern enough, we don't need to install + if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) return; + + // Install interception and make sure it sticks! + Os def = null; + do { + def = Os.getDefault(); + } while (!Os.compareAndSetDefault(def, new AndroidOs(def))); + } + + private AndroidOs(Os os) { + super(os); + } + + private FileDescriptor openDeprecatedDataPath(String path, int mode) throws ErrnoException { + final Uri uri = ContentResolver.translateDeprecatedDataPath(path); + Log.v(TAG, "Redirecting " + path + " to " + uri); + + final ContentResolver cr = currentActivityThread().getApplication() + .getContentResolver(); + try { + final FileDescriptor fd = new FileDescriptor(); + fd.setInt$(cr.openFileDescriptor(uri, + FileUtils.translateModePosixToString(mode)).detachFd()); + return fd; + } catch (FileNotFoundException e) { + throw new ErrnoException(e.getMessage(), OsConstants.ENOENT); + } + } + + @Override + public boolean access(String path, int mode) throws ErrnoException { + if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) { + // If we opened it okay, then access check succeeded + IoUtils.closeQuietly( + openDeprecatedDataPath(path, FileUtils.translateModeAccessToPosix(mode))); + return true; + } else { + return super.access(path, mode); + } + } + + @Override + public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { + if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) { + return openDeprecatedDataPath(path, mode); + } else { + return super.open(path, flags, mode); + } + } + + @Override + public StructStat stat(String path) throws ErrnoException { + if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) { + final FileDescriptor fd = openDeprecatedDataPath(path, OsConstants.O_RDONLY); + try { + return android.system.Os.fstat(fd); + } finally { + IoUtils.closeQuietly(fd); + } + } else { + return super.stat(path); } } } @@ -6777,6 +6864,9 @@ public final class ActivityThread extends ClientTransactionHandler { public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); + // Install selective syscall interception + AndroidOs.install(); + // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index a2a6b9b4a762..4de1dfcc12ba 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -52,7 +52,9 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.UserHandle; +import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -88,6 +90,30 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public abstract class ContentResolver { /** + * Enables logic that supports deprecation of {@code _data} columns, + * typically by replacing values with fake paths that the OS then offers to + * redirect to {@link #openFileDescriptor(Uri, String)}, which developers + * should be using directly. + * + * @hide + */ + public static final boolean DEPRECATE_DATA_COLUMNS = SystemProperties + .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); + + /** + * Special filesystem path prefix which indicates that a path should be + * treated as a {@code content://} {@link Uri} when + * {@link #DEPRECATE_DATA_COLUMNS} is enabled. + * <p> + * The remainder of the path after this prefix is a + * {@link Uri#getSchemeSpecificPart()} value, which includes authority, path + * segments, and query parameters. + * + * @hide + */ + public static final String DEPRECATE_DATA_PREFIX = "/mnt/content/"; + + /** * @deprecated instead use * {@link #requestSync(android.accounts.Account, String, android.os.Bundle)} */ @@ -3261,4 +3287,16 @@ public abstract class ContentResolver { e.rethrowFromSystemServer(); } } + + /** {@hide} */ + public static Uri translateDeprecatedDataPath(String path) { + final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length()); + return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT) + .encodedOpaquePart(ssp).build().toString()); + } + + /** {@hide} */ + public static String translateDeprecatedDataPath(Uri uri) { + return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2); + } } diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index f71fdd7fdac1..0b90f5437826 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -22,16 +22,19 @@ 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.F_OK; 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.R_OK; import static android.system.OsConstants.SPLICE_F_MORE; import static android.system.OsConstants.SPLICE_F_MOVE; import static android.system.OsConstants.S_ISFIFO; import static android.system.OsConstants.S_ISREG; +import static android.system.OsConstants.W_OK; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1300,6 +1303,23 @@ public class FileUtils { } /** {@hide} */ + public static int translateModeAccessToPosix(int mode) { + if (mode == F_OK) { + // There's not an exact mapping, so we attempt a read-only open to + // determine if a file exists + return O_RDONLY; + } else if ((mode & (R_OK | W_OK)) == (R_OK | W_OK)) { + return O_RDWR; + } else if ((mode & R_OK) == R_OK) { + return O_RDONLY; + } else if ((mode & W_OK) == W_OK) { + return O_WRONLY; + } else { + throw new IllegalArgumentException("Bad mode: " + mode); + } + } + + /** {@hide} */ @VisibleForTesting public static class MemoryPipe extends Thread implements AutoCloseable { private final FileDescriptor[] pipe; diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 0aab76ebd0e0..1fce8e6c9ac2 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -60,7 +60,10 @@ import java.util.List; public final class MediaStore { private final static String TAG = "MediaStore"; + /** The authority for the media provider */ public static final String AUTHORITY = "media"; + /** A content:// style uri to the authority for the media provider */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; @@ -2254,6 +2257,18 @@ public final class MediaStore { } /** + * Return the volume name that the given {@link Uri} references. + */ + public static @NonNull String getVolumeName(@NonNull Uri uri) { + final List<String> segments = uri.getPathSegments(); + if (uri.getAuthority().equals(AUTHORITY) && segments != null && segments.size() > 0) { + return segments.get(0); + } else { + throw new IllegalArgumentException("Not a media Uri: " + uri); + } + } + + /** * Uri for querying the state of the media scanner. */ public static Uri getMediaScannerUri() { diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 0036186994fe..9940bf7dd692 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -158,4 +158,19 @@ public class ContentResolverTest { assertImageAspectAndContents(res); } + + @Test + public void testTranslateDeprecatedDataPath() throws Exception { + assertTranslate(Uri.parse("content://com.example/path/?foo=bar&baz=meow")); + assertTranslate(Uri.parse("content://com.example/path/subpath/12/")); + assertTranslate(Uri.parse("content://com.example/path/subpath/12")); + assertTranslate(Uri.parse("content://com.example/path/12")); + assertTranslate(Uri.parse("content://com.example/")); + assertTranslate(Uri.parse("content://com.example")); + } + + private static void assertTranslate(Uri uri) { + assertEquals(uri, ContentResolver + .translateDeprecatedDataPath(ContentResolver.translateDeprecatedDataPath(uri))); + } } diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 6966448f7d63..55e21a76f170 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -17,6 +17,7 @@ package android.os; import static android.os.FileUtils.roundStorageSize; +import static android.os.FileUtils.translateModeAccessToPosix; import static android.os.FileUtils.translateModePfdToPosix; import static android.os.FileUtils.translateModePosixToPfd; import static android.os.FileUtils.translateModePosixToString; @@ -27,12 +28,16 @@ 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.F_OK; 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.R_OK; +import static android.system.OsConstants.W_OK; +import static android.system.OsConstants.X_OK; 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; @@ -525,6 +530,15 @@ public class FileUtilsTest { } } + @Test + public void testTranslateMode_Access() throws Exception { + assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK)); + assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK)); + assertEquals(O_WRONLY, translateModeAccessToPosix(W_OK)); + assertEquals(O_RDWR, translateModeAccessToPosix(R_OK | W_OK)); + assertEquals(O_RDWR, translateModeAccessToPosix(R_OK | W_OK | X_OK)); + } + private static void assertTranslate(String string, int posix, int pfd) { assertEquals(posix, translateModeStringToPosix(string)); assertEquals(string, translateModePosixToString(posix)); |