diff --git a/api/current.txt b/api/current.txt index 15392b8fe29a9..c8d842ddcfb35 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 4512fc3b9c2b1..fb97643129bd9 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 4756bf40bad3b..ee7d00208e2c6 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 a2a6b9b4a7624..4de1dfcc12ba2 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; @@ -87,6 +89,30 @@ import java.util.concurrent.atomic.AtomicBoolean; * developer guide.
*/ 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. + *
+ * 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 f71fdd7fdac1f..0b90f54378261 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;
@@ -1299,6 +1302,23 @@ public class FileUtils {
return res;
}
+ /** {@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 {
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 0aab76ebd0e07..1fce8e6c9ac22 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 + "/";
@@ -2253,6 +2256,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