OSDN Git Service

RESTRICT AUTOMERGE: Recover shady content:// paths.
[android-x86/frameworks-base.git] / core / java / android / content / ContentProvider.java
index 0cff4c0..e2c898d 100644 (file)
 
 package android.content;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Configuration;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.SQLException;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -33,12 +39,13 @@ import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
-import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -46,6 +53,8 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Content providers are one of the primary building blocks of Android applications, providing
@@ -89,6 +98,7 @@ import java.util.ArrayList;
  * developer guide.</p>
  */
 public abstract class ContentProvider implements ComponentCallbacks2 {
+
     private static final String TAG = "ContentProvider";
 
     /*
@@ -110,7 +120,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     private boolean mNoPerms;
     private boolean mSingleUser;
 
-    private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>();
+    private final ThreadLocal<String> mCallingPackage = new ThreadLocal<>();
 
     private Transport mTransport = new Transport();
 
@@ -197,19 +207,39 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
         }
 
         @Override
-        public Cursor query(String callingPkg, Uri uri, String[] projection,
-                String selection, String[] selectionArgs, String sortOrder,
-                ICancellationSignal cancellationSignal) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+        public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection,
+                @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) {
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
-                return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
+                // The caller has no access to the data, so return an empty cursor with
+                // the columns in the requested order. The caller may ask for an invalid
+                // column and we would not catch that but this is not a problem in practice.
+                // We do not call ContentProvider#query with a modified where clause since
+                // the implementation is not guaranteed to be backed by a SQL database, hence
+                // it may not handle properly the tautology where clause we would have created.
+                if (projection != null) {
+                    return new MatrixCursor(projection, 0);
+                }
+
+                // Null projection means all columns but we have no idea which they are.
+                // However, the caller may be expecting to access them my index. Hence,
+                // we have to execute the query as if allowed to get a cursor with the
+                // columns. We then use the column names to return an empty cursor.
+                Cursor cursor = ContentProvider.this.query(
+                        uri, projection, queryArgs,
                         CancellationSignal.fromTransport(cancellationSignal));
+                if (cursor == null) {
+                    return null;
+                }
+
+                // Return an empty cursor for all columns.
+                return new MatrixCursor(cursor.getColumnNames(), 0);
             }
             final String original = setCallingPackage(callingPkg);
             try {
                 return ContentProvider.this.query(
-                        uri, projection, selection, selectionArgs, sortOrder,
+                        uri, projection, queryArgs,
                         CancellationSignal.fromTransport(cancellationSignal));
             } finally {
                 setCallingPackage(original);
@@ -218,16 +248,16 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public String getType(Uri uri) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             return ContentProvider.this.getType(uri);
         }
 
         @Override
         public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
-            validateIncomingUri(uri);
+            uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                 return rejectInsert(uri, initialValues);
             }
@@ -241,8 +271,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
@@ -263,11 +293,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
             for (int i = 0; i < numOperations; i++) {
                 ContentProviderOperation operation = operations.get(i);
                 Uri uri = operation.getUri();
-                validateIncomingUri(uri);
                 userIds[i] = getUserIdFromUri(uri);
-                if (userIds[i] != UserHandle.USER_CURRENT) {
-                    // Removing the user id from the uri.
-                    operation = new ContentProviderOperation(operation, true);
+                uri = validateIncomingUri(uri);
+                uri = maybeGetUriWithoutUserId(uri);
+                // Rebuild operation if we changed the Uri above
+                if (!Objects.equals(operation.getUri(), uri)) {
+                    operation = new ContentProviderOperation(operation, uri);
                     operations.set(i, operation);
                 }
                 if (operation.isReadOperation()) {
@@ -302,8 +333,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
@@ -318,8 +349,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
         @Override
         public int update(String callingPkg, Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             if (enforceWritePermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
                 return 0;
             }
@@ -335,8 +366,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
         public ParcelFileDescriptor openFile(
                 String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal,
                 IBinder callerToken) throws FileNotFoundException {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, mode, callerToken);
             final String original = setCallingPackage(callingPkg);
             try {
@@ -351,8 +382,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
         public AssetFileDescriptor openAssetFile(
                 String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
                 throws FileNotFoundException {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, mode, null);
             final String original = setCallingPackage(callingPkg);
             try {
@@ -364,7 +395,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
         }
 
         @Override
-        public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
+        public Bundle call(
+                String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) {
+            Bundle.setDefusable(extras, true);
             final String original = setCallingPackage(callingPkg);
             try {
                 return ContentProvider.this.call(method, arg, extras);
@@ -375,16 +408,17 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
         }
 
         @Override
         public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
                 Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
-            validateIncomingUri(uri);
-            uri = getUriWithoutUserId(uri);
+            Bundle.setDefusable(opts, true);
+            uri = validateIncomingUri(uri);
+            uri = maybeGetUriWithoutUserId(uri);
             enforceFilePermission(callingPkg, uri, "r", null);
             final String original = setCallingPackage(callingPkg);
             try {
@@ -402,7 +436,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public Uri canonicalize(String callingPkg, Uri uri) {
-            validateIncomingUri(uri);
+            uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
@@ -418,7 +452,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         @Override
         public Uri uncanonicalize(String callingPkg, Uri uri) {
-            validateIncomingUri(uri);
+            uri = validateIncomingUri(uri);
             int userId = getUserIdFromUri(uri);
             uri = getUriWithoutUserId(uri);
             if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
@@ -432,6 +466,23 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
             }
         }
 
+        @Override
+        public boolean refresh(String callingPkg, Uri uri, Bundle args,
+                ICancellationSignal cancellationSignal) throws RemoteException {
+            uri = validateIncomingUri(uri);
+            uri = getUriWithoutUserId(uri);
+            if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+            final String original = setCallingPackage(callingPkg);
+            try {
+                return ContentProvider.this.refresh(uri, args,
+                        CancellationSignal.fromTransport(cancellationSignal));
+            } finally {
+                setCallingPackage(original);
+            }
+        }
+
         private void enforceFilePermission(String callingPkg, Uri uri, String mode,
                 IBinder callerToken) throws FileNotFoundException, SecurityException {
             if (mode != null && mode.indexOf('w') != -1) {
@@ -449,19 +500,29 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
         private int enforceReadPermission(String callingPkg, Uri uri, IBinder callerToken)
                 throws SecurityException {
-            enforceReadPermissionInner(uri, callerToken);
+            final int mode = enforceReadPermissionInner(uri, callingPkg, callerToken);
+            if (mode != MODE_ALLOWED) {
+                return mode;
+            }
+
             if (mReadOp != AppOpsManager.OP_NONE) {
-                return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg);
+                return mAppOpsManager.noteProxyOp(mReadOp, callingPkg);
             }
+
             return AppOpsManager.MODE_ALLOWED;
         }
 
         private int enforceWritePermission(String callingPkg, Uri uri, IBinder callerToken)
                 throws SecurityException {
-            enforceWritePermissionInner(uri, callerToken);
+            final int mode = enforceWritePermissionInner(uri, callingPkg, callerToken);
+            if (mode != MODE_ALLOWED) {
+                return mode;
+            }
+
             if (mWriteOp != AppOpsManager.OP_NONE) {
-                return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg);
+                return mAppOpsManager.noteProxyOp(mWriteOp, callingPkg);
             }
+
             return AppOpsManager.MODE_ALLOWED;
         }
     }
@@ -473,26 +534,47 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
                 == PERMISSION_GRANTED;
     }
 
+    /**
+     * Verify that calling app holds both the given permission and any app-op
+     * associated with that permission.
+     */
+    private int checkPermissionAndAppOp(String permission, String callingPkg,
+            IBinder callerToken) {
+        if (getContext().checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid(),
+                callerToken) != PERMISSION_GRANTED) {
+            return MODE_ERRORED;
+        }
+
+        final int permOp = AppOpsManager.permissionToOpCode(permission);
+        if (permOp != AppOpsManager.OP_NONE) {
+            return mTransport.mAppOpsManager.noteProxyOp(permOp, callingPkg);
+        }
+
+        return MODE_ALLOWED;
+    }
+
     /** {@hide} */
-    protected void enforceReadPermissionInner(Uri uri, IBinder callerToken)
+    protected int enforceReadPermissionInner(Uri uri, String callingPkg, IBinder callerToken)
             throws SecurityException {
         final Context context = getContext();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         String missingPerm = null;
+        int strongestMode = MODE_ALLOWED;
 
         if (UserHandle.isSameApp(uid, mMyUid)) {
-            return;
+            return MODE_ALLOWED;
         }
 
         if (mExported && checkUser(pid, uid, context)) {
             final String componentPerm = getReadPermission();
             if (componentPerm != null) {
-                if (context.checkPermission(componentPerm, pid, uid, callerToken)
-                        == PERMISSION_GRANTED) {
-                    return;
+                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+                if (mode == MODE_ALLOWED) {
+                    return MODE_ALLOWED;
                 } else {
                     missingPerm = componentPerm;
+                    strongestMode = Math.max(strongestMode, mode);
                 }
             }
 
@@ -506,14 +588,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
                 for (PathPermission pp : pps) {
                     final String pathPerm = pp.getReadPermission();
                     if (pathPerm != null && pp.match(path)) {
-                        if (context.checkPermission(pathPerm, pid, uid, callerToken)
-                                == PERMISSION_GRANTED) {
-                            return;
+                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, callerToken);
+                        if (mode == MODE_ALLOWED) {
+                            return MODE_ALLOWED;
                         } else {
                             // any denied <path-permission> means we lose
                             // default <provider> access.
                             allowDefaultRead = false;
                             missingPerm = pathPerm;
+                            strongestMode = Math.max(strongestMode, mode);
                         }
                     }
                 }
@@ -521,7 +604,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
             // if we passed <path-permission> checks above, and no default
             // <provider> permission, then allow access.
-            if (allowDefaultRead) return;
+            if (allowDefaultRead) return MODE_ALLOWED;
         }
 
         // last chance, check against any uri grants
@@ -530,37 +613,50 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
                 ? maybeAddUserId(uri, callingUserId) : uri;
         if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION,
                 callerToken) == PERMISSION_GRANTED) {
-            return;
+            return MODE_ALLOWED;
         }
 
-        final String failReason = mExported
-                ? " requires " + missingPerm + ", or grantUriPermission()"
-                : " requires the provider be exported, or grantUriPermission()";
+        // If the worst denial we found above was ignored, then pass that
+        // ignored through; otherwise we assume it should be a real error below.
+        if (strongestMode == MODE_IGNORED) {
+            return MODE_IGNORED;
+        }
+
+        final String suffix;
+        if (android.Manifest.permission.MANAGE_DOCUMENTS.equals(mReadPermission)) {
+            suffix = " requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs";
+        } else if (mExported) {
+            suffix = " requires " + missingPerm + ", or grantUriPermission()";
+        } else {
+            suffix = " requires the provider be exported, or grantUriPermission()";
+        }
         throw new SecurityException("Permission Denial: reading "
                 + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid
-                + ", uid=" + uid + failReason);
+                + ", uid=" + uid + suffix);
     }
 
     /** {@hide} */
-    protected void enforceWritePermissionInner(Uri uri, IBinder callerToken)
+    protected int enforceWritePermissionInner(Uri uri, String callingPkg, IBinder callerToken)
             throws SecurityException {
         final Context context = getContext();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         String missingPerm = null;
+        int strongestMode = MODE_ALLOWED;
 
         if (UserHandle.isSameApp(uid, mMyUid)) {
-            return;
+            return MODE_ALLOWED;
         }
 
         if (mExported && checkUser(pid, uid, context)) {
             final String componentPerm = getWritePermission();
             if (componentPerm != null) {
-                if (context.checkPermission(componentPerm, pid, uid, callerToken)
-                        == PERMISSION_GRANTED) {
-                    return;
+                final int mode = checkPermissionAndAppOp(componentPerm, callingPkg, callerToken);
+                if (mode == MODE_ALLOWED) {
+                    return MODE_ALLOWED;
                 } else {
                     missingPerm = componentPerm;
+                    strongestMode = Math.max(strongestMode, mode);
                 }
             }
 
@@ -574,14 +670,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
                 for (PathPermission pp : pps) {
                     final String pathPerm = pp.getWritePermission();
                     if (pathPerm != null && pp.match(path)) {
-                        if (context.checkPermission(pathPerm, pid, uid, callerToken)
-                                == PERMISSION_GRANTED) {
-                            return;
+                        final int mode = checkPermissionAndAppOp(pathPerm, callingPkg, callerToken);
+                        if (mode == MODE_ALLOWED) {
+                            return MODE_ALLOWED;
                         } else {
                             // any denied <path-permission> means we lose
                             // default <provider> access.
                             allowDefaultWrite = false;
                             missingPerm = pathPerm;
+                            strongestMode = Math.max(strongestMode, mode);
                         }
                     }
                 }
@@ -589,13 +686,19 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
 
             // if we passed <path-permission> checks above, and no default
             // <provider> permission, then allow access.
-            if (allowDefaultWrite) return;
+            if (allowDefaultWrite) return MODE_ALLOWED;
         }
 
         // last chance, check against any uri grants
         if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                 callerToken) == PERMISSION_GRANTED) {
-            return;
+            return MODE_ALLOWED;
+        }
+
+        // If the worst denial we found above was ignored, then pass that
+        // ignored through; otherwise we assume it should be a real error below.
+        if (strongestMode == MODE_IGNORED) {
+            return MODE_IGNORED;
         }
 
         final String failReason = mExported
@@ -611,7 +714,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * {@link #onCreate} has been called -- this will return {@code null} in the
      * constructor.
      */
-    public final Context getContext() {
+    public final @Nullable Context getContext() {
         return mContext;
     }
 
@@ -639,7 +742,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @throws SecurityException if the calling package doesn't belong to the
      *             calling UID.
      */
-    public final String getCallingPackage() {
+    public final @Nullable String getCallingPackage() {
         final String pkg = mCallingPackage.get();
         if (pkg != null) {
             mTransport.mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
@@ -688,7 +791,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *
      * @param permission Name of the permission required for read-only access.
      */
-    protected final void setReadPermission(String permission) {
+    protected final void setReadPermission(@Nullable String permission) {
         mReadPermission = permission;
     }
 
@@ -699,7 +802,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
      * and Threads</a>.
      */
-    public final String getReadPermission() {
+    public final @Nullable String getReadPermission() {
         return mReadPermission;
     }
 
@@ -710,7 +813,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *
      * @param permission Name of the permission required for read/write access.
      */
-    protected final void setWritePermission(String permission) {
+    protected final void setWritePermission(@Nullable String permission) {
         mWritePermission = permission;
     }
 
@@ -721,7 +824,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
      * and Threads</a>.
      */
-    public final String getWritePermission() {
+    public final @Nullable String getWritePermission() {
         return mWritePermission;
     }
 
@@ -732,7 +835,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *
      * @param permissions Array of path permission descriptions.
      */
-    protected final void setPathPermissions(PathPermission[] permissions) {
+    protected final void setPathPermissions(@Nullable PathPermission[] permissions) {
         mPathPermissions = permissions;
     }
 
@@ -743,7 +846,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
      * and Threads</a>.
      */
-    public final PathPermission[] getPathPermissions() {
+    public final @Nullable PathPermission[] getPathPermissions() {
         return mPathPermissions;
     }
 
@@ -797,6 +900,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * (Content providers do not usually care about things like screen
      * orientation, but may want to know about locale changes.)
      */
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
     }
 
@@ -808,40 +912,22 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * <p>The default content provider implementation does nothing.
      * Subclasses may override this method to take appropriate action.
      */
+    @Override
     public void onLowMemory() {
     }
 
+    @Override
     public void onTrimMemory(int level) {
     }
 
     /**
-     * @hide
-     * Implementation when a caller has performed a query on the content
-     * provider, but that call has been rejected for the operation given
-     * to {@link #setAppOps(int, int)}.  The default implementation
-     * rewrites the <var>selection</var> argument to include a condition
-     * that is never true (so will always result in an empty cursor)
-     * and calls through to {@link #query(android.net.Uri, String[], String, String[],
-     * String, android.os.CancellationSignal)} with that.
-     */
-    public Cursor rejectQuery(Uri uri, String[] projection,
-            String selection, String[] selectionArgs, String sortOrder,
-            CancellationSignal cancellationSignal) {
-        // The read is not allowed...  to fake it out, we replace the given
-        // selection statement with a dummy one that will always be false.
-        // This way we will get a cursor back that has the correct structure
-        // but contains no rows.
-        if (selection == null || selection.isEmpty()) {
-            selection = "'A' = 'B'";
-        } else {
-            selection = "'A' = 'B' AND (" + selection + ")";
-        }
-        return query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
-    }
-
-    /**
      * Implement this to handle query requests from clients.
-     * This method can be called from multiple threads, as described in
+     *
+     * <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
+     * {@link #query(Uri, String[], Bundle, CancellationSignal)} and provide a stub
+     * implementation of this method.
+     *
+     * <p>This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
      * and Threads</a>.
      * <p>
@@ -894,12 +980,17 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *      If {@code null} then the provider is free to define the sort order.
      * @return a Cursor or {@code null}.
      */
-    public abstract Cursor query(Uri uri, String[] projection,
-            String selection, String[] selectionArgs, String sortOrder);
+    public abstract @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder);
 
     /**
      * Implement this to handle query requests from clients with support for cancellation.
-     * This method can be called from multiple threads, as described in
+     *
+     * <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
+     * {@link #query(Uri, String[], Bundle, CancellationSignal)} instead of this method.
+     *
+     * <p>This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
      * and Threads</a>.
      * <p>
@@ -956,17 +1047,115 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @param sortOrder How the rows in the cursor should be sorted.
      *      If {@code null} then the provider is free to define the sort order.
      * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if none.
-     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * If the operation is canceled, then {@link android.os.OperationCanceledException} will be thrown
      * when the query is executed.
      * @return a Cursor or {@code null}.
      */
-    public Cursor query(Uri uri, String[] projection,
-            String selection, String[] selectionArgs, String sortOrder,
-            CancellationSignal cancellationSignal) {
+    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
         return query(uri, projection, selection, selectionArgs, sortOrder);
     }
 
     /**
+     * Implement this to handle query requests where the arguments are packed into a {@link Bundle}.
+     * Arguments may include traditional SQL style query arguments. When present these
+     * should be handled  according to the contract established in
+     * {@link #query(Uri, String[], String, String[], String, CancellationSignal).
+     *
+     * <p>Traditional SQL arguments can be found in the bundle using the following keys:
+     * <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION}
+     * <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
+     * <li>{@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER}
+     *
+     * <p>This method can be called from multiple threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
+     * and Threads</a>.
+     *
+     * <p>
+     * Example client call:<p>
+     * <pre>// Request 20 records starting at row index 30.
+       Bundle queryArgs = new Bundle();
+       queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 30);
+       queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 20);
+
+       Cursor cursor = getContentResolver().query(
+                contentUri,    // Content Uri is specific to individual content providers.
+                projection,    // String[] describing which columns to return.
+                queryArgs,     // Query arguments.
+                null);         // Cancellation signal.</pre>
+     *
+     * Example implementation:<p>
+     * <pre>
+
+        int recordsetSize = 0x1000;  // Actual value is implementation specific.
+        queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;  // ensure queryArgs is non-null
+
+        int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
+        int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MIN_VALUE);
+
+        MatrixCursor c = new MatrixCursor(PROJECTION, limit);
+
+        // Calculate the number of items to include in the cursor.
+        int numItems = MathUtils.constrain(recordsetSize - offset, 0, limit);
+
+        // Build the paged result set....
+        for (int i = offset; i < offset + numItems; i++) {
+            // populate row from your data.
+        }
+
+        Bundle extras = new Bundle();
+        c.setExtras(extras);
+
+        // Any QUERY_ARG_* key may be included if honored.
+        // In an actual implementation, include only keys that are both present in queryArgs
+        // and reflected in the Cursor output. For example, if QUERY_ARG_OFFSET were included
+        // in queryArgs, but was ignored because it contained an invalid value (like –273),
+        // then QUERY_ARG_OFFSET should be omitted.
+        extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[] {
+            ContentResolver.QUERY_ARG_OFFSET,
+            ContentResolver.QUERY_ARG_LIMIT
+        });
+
+        extras.putInt(ContentResolver.EXTRA_TOTAL_COUNT, recordsetSize);
+
+        cursor.setNotificationUri(getContext().getContentResolver(), uri);
+
+        return cursor;</pre>
+     * <p>
+     * @see #query(Uri, String[], String, String[], String, CancellationSignal) for
+     *     implementation details.
+     *
+     * @param uri The URI to query. This will be the full URI sent by the client.
+     * @param projection The list of columns to put into the cursor.
+     *            If {@code null} provide a default set of columns.
+     * @param queryArgs A Bundle containing all additional information necessary for the query.
+     *            Values in the Bundle may include SQL style arguments.
+     * @param cancellationSignal A signal to cancel the operation in progress,
+     *            or {@code null}.
+     * @return a Cursor or {@code null}.
+     */
+    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+        queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
+
+        // if client doesn't supply an SQL sort order argument, attempt to build one from
+        // QUERY_ARG_SORT* arguments.
+        String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
+        if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
+            sortClause = ContentResolver.createSqlSortClause(queryArgs);
+        }
+
+        return query(
+                uri,
+                projection,
+                queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
+                queryArgs.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS),
+                sortClause,
+                cancellationSignal);
+    }
+
+    /**
      * Implement this to handle requests for the MIME type of the data at the
      * given URI.  The returned MIME type should start with
      * <code>vnd.android.cursor.item</code> for a single record,
@@ -984,7 +1173,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @param uri the URI to query.
      * @return a MIME type string, or {@code null} if there is no type.
      */
-    public abstract String getType(Uri uri);
+    public abstract @Nullable String getType(@NonNull Uri uri);
 
     /**
      * Implement this to support canonicalization of URIs that refer to your
@@ -1016,7 +1205,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @return Return the canonical representation of <var>url</var>, or null if
      * canonicalization of that Uri is not supported.
      */
-    public Uri canonicalize(Uri url) {
+    public @Nullable Uri canonicalize(@NonNull Uri url) {
         return null;
     }
 
@@ -1034,11 +1223,38 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * the data identified by the canonical representation can not be found in
      * the current environment.
      */
-    public Uri uncanonicalize(Uri url) {
+    public @Nullable Uri uncanonicalize(@NonNull Uri url) {
         return url;
     }
 
     /**
+     * Implement this to support refresh of content identified by {@code uri}. By default, this
+     * method returns false; providers who wish to implement this should return true to signal the
+     * client that the provider has tried refreshing with its own implementation.
+     * <p>
+     * This allows clients to request an explicit refresh of content identified by {@code uri}.
+     * <p>
+     * Client code should only invoke this method when there is a strong indication (such as a user
+     * initiated pull to refresh gesture) that the content is stale.
+     * <p>
+     * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)}
+     * notifications when content changes.
+     *
+     * @param uri The Uri identifying the data to refresh.
+     * @param args Additional options from the client. The definitions of these are specific to the
+     *            content provider being called.
+     * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if
+     *            none. For example, if you called refresh on a particular uri, you should call
+     *            {@link CancellationSignal#throwIfCanceled()} to check whether the client has
+     *            canceled the refresh request.
+     * @return true if the provider actually tried refreshing.
+     */
+    public boolean refresh(Uri uri, @Nullable Bundle args,
+            @Nullable CancellationSignal cancellationSignal) {
+        return false;
+    }
+
+    /**
      * @hide
      * Implementation when a caller has performed an insert on the content
      * provider, but that call has been rejected for the operation given
@@ -1067,7 +1283,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *     This must not be {@code null}.
      * @return The URI for the newly inserted item.
      */
-    public abstract Uri insert(Uri uri, ContentValues values);
+    public abstract @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values);
 
     /**
      * Override this to handle requests to insert a set of new rows, or the
@@ -1084,7 +1300,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      *    This must not be {@code null}.
      * @return The number of values that were inserted.
      */
-    public int bulkInsert(Uri uri, ContentValues[] values) {
+    public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
         int numValues = values.length;
         for (int i = 0; i < numValues; i++) {
             insert(uri, values[i]);
@@ -1112,7 +1328,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @return The number of rows affected.
      * @throws SQLException
      */
-    public abstract int delete(Uri uri, String selection, String[] selectionArgs);
+    public abstract int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs);
 
     /**
      * Implement this to handle requests to update one or more rows.
@@ -1131,8 +1348,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @param selection An optional filter to match rows to update.
      * @return the number of rows affected.
      */
-    public abstract int update(Uri uri, ContentValues values, String selection,
-            String[] selectionArgs);
+    public abstract int update(@NonNull Uri uri, @Nullable ContentValues values,
+            @Nullable String selection, @Nullable String[] selectionArgs);
 
     /**
      * Override this to handle requests to open a file blob.
@@ -1160,6 +1377,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)},
      * {@link ParcelFileDescriptor#createReliablePipe()}, or
      * {@link ParcelFileDescriptor#createReliableSocketPair()}.
+     * <p>
+     * If you need to return a large file that isn't backed by a real file on
+     * disk, such as a file on a network share or cloud storage service,
+     * consider using
+     * {@link StorageManager#openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler)}
+     * which will let you to stream the content on-demand.
      *
      * <p class="note">For use in Intents, you will want to implement {@link #getType}
      * to return the appropriate MIME type for the data returned here with
@@ -1191,7 +1414,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #getType(android.net.Uri)
      * @see ParcelFileDescriptor#parseMode(String)
      */
-    public ParcelFileDescriptor openFile(Uri uri, String mode)
+    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
             throws FileNotFoundException {
         throw new FileNotFoundException("No files supported by provider at "
                 + uri);
@@ -1261,8 +1484,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #getType(android.net.Uri)
      * @see ParcelFileDescriptor#parseMode(String)
      */
-    public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
-            throws FileNotFoundException {
+    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
+            @Nullable CancellationSignal signal) throws FileNotFoundException {
         return openFile(uri, mode);
     }
 
@@ -1312,12 +1535,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * no file associated with the given URI or the mode is invalid.
      * @throws SecurityException Throws SecurityException if the caller does
      * not have permission to access the file.
-     * 
+     *
      * @see #openFile(Uri, String)
      * @see #openFileHelper(Uri, String)
      * @see #getType(android.net.Uri)
      */
-    public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
             throws FileNotFoundException {
         ParcelFileDescriptor fd = openFile(uri, mode);
         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
@@ -1380,8 +1603,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #openFileHelper(Uri, String)
      * @see #getType(android.net.Uri)
      */
-    public AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
-            throws FileNotFoundException {
+    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
+            @Nullable CancellationSignal signal) throws FileNotFoundException {
         return openAssetFile(uri, mode);
     }
 
@@ -1399,8 +1622,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @return Returns a new ParcelFileDescriptor that can be used by the
      * client to access the file.
      */
-    protected final ParcelFileDescriptor openFileHelper(Uri uri,
-            String mode) throws FileNotFoundException {
+    protected final @NonNull ParcelFileDescriptor openFileHelper(@NonNull Uri uri,
+            @NonNull String mode) throws FileNotFoundException {
         Cursor c = query(uri, new String[]{"_data"}, null, null, null);
         int count = (c != null) ? c.getCount() : 0;
         if (count != 1) {
@@ -1446,7 +1669,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #openTypedAssetFile(Uri, String, Bundle)
      * @see ClipDescription#compareMimeTypes(String, String)
      */
-    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+    public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter) {
         return null;
     }
 
@@ -1495,8 +1718,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #openAssetFile(Uri, String)
      * @see ClipDescription#compareMimeTypes(String, String)
      */
-    public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
-            throws FileNotFoundException {
+    public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+            @NonNull String mimeTypeFilter, @Nullable Bundle opts) throws FileNotFoundException {
         if ("*/*".equals(mimeTypeFilter)) {
             // If they can take anything, the untyped open call is good enough.
             return openAssetFile(uri, "r");
@@ -1562,9 +1785,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @see #openAssetFile(Uri, String)
      * @see ClipDescription#compareMimeTypes(String, String)
      */
-    public AssetFileDescriptor openTypedAssetFile(
-            Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
-            throws FileNotFoundException {
+    public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
+            @NonNull String mimeTypeFilter, @Nullable Bundle opts,
+            @Nullable CancellationSignal signal) throws FileNotFoundException {
         return openTypedAssetFile(uri, mimeTypeFilter, opts);
     }
 
@@ -1586,8 +1809,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
          * @param opts Options supplied by caller.
          * @param args Your own custom arguments.
          */
-        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
-                Bundle opts, T args);
+        public void writeDataToPipe(@NonNull ParcelFileDescriptor output, @NonNull Uri uri,
+                @NonNull String mimeType, @Nullable Bundle opts, @Nullable T args);
     }
 
     /**
@@ -1607,9 +1830,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * the pipe.  This should be returned to the caller for reading; the caller
      * is responsible for closing it when done.
      */
-    public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType,
-            final Bundle opts, final T args, final PipeDataWriter<T> func)
-            throws FileNotFoundException {
+    public @NonNull <T> ParcelFileDescriptor openPipeHelper(final @NonNull Uri uri,
+            final @NonNull String mimeType, final @Nullable Bundle opts, final @Nullable T args,
+            final @NonNull PipeDataWriter<T> func) throws FileNotFoundException {
         try {
             final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
 
@@ -1680,7 +1903,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
          */
         if (mContext == null) {
             mContext = context;
-            if (context != null) {
+            if (context != null && mTransport != null) {
                 mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                         Context.APP_OPS_SERVICE);
             }
@@ -1714,8 +1937,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @throws OperationApplicationException thrown if any operation fails.
      * @see ContentProviderOperation#apply
      */
-    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
-            throws OperationApplicationException {
+    public @NonNull ContentProviderResult[] applyBatch(
+            @NonNull ArrayList<ContentProviderOperation> operations)
+                    throws OperationApplicationException {
         final int numOperations = operations.size();
         final ContentProviderResult[] results = new ContentProviderResult[numOperations];
         for (int i = 0; i < numOperations; i++) {
@@ -1742,14 +1966,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
      * @return provider-defined return value.  May be {@code null}, which is also
      *   the default for providers which don't implement any call methods.
      */
-    public Bundle call(String method, String arg, Bundle extras) {
+    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
+            @Nullable Bundle extras) {
         return null;
     }
 
     /**
      * Implement this to shut down the ContentProvider instance. You can then
      * invoke this method in unit tests.
-     * 
+     *
      * <p>
      * Android normally handles ContentProvider startup and shutdown
      * automatically. You do not need to start up or shut down a
@@ -1787,12 +2012,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
     }
 
     /** @hide */
-    private void validateIncomingUri(Uri uri) throws SecurityException {
+    public Uri validateIncomingUri(Uri uri) throws SecurityException {
         String auth = uri.getAuthority();
-        int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
-        if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
-            throw new SecurityException("trying to query a ContentProvider in user "
-                    + mContext.getUserId() + " with a uri belonging to user " + userId);
+        if (!mSingleUser) {
+            int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
+            if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
+                throw new SecurityException("trying to query a ContentProvider in user "
+                        + mContext.getUserId() + " with a uri belonging to user " + userId);
+            }
         }
         if (!matchesOurAuthorities(getAuthorityWithoutUserId(auth))) {
             String message = "The authority of the uri " + uri + " does not match the one of the "
@@ -1800,10 +2027,31 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
             if (mAuthority != null) {
                 message += mAuthority;
             } else {
-                message += mAuthorities;
+                message += Arrays.toString(mAuthorities);
             }
             throw new SecurityException(message);
         }
+
+        // Normalize the path by removing any empty path segments, which can be
+        // a source of security issues.
+        final String encodedPath = uri.getEncodedPath();
+        if (encodedPath != null && encodedPath.indexOf("//") != -1) {
+            final Uri normalized = uri.buildUpon()
+                    .encodedPath(encodedPath.replaceAll("//+", "/")).build();
+            Log.w(TAG, "Normalized " + uri + " to " + normalized
+                    + " to avoid possible security issues");
+            return normalized;
+        } else {
+            return uri;
+        }
+    }
+
+    /** @hide */
+    private Uri maybeGetUriWithoutUserId(Uri uri) {
+        if (mSingleUser) {
+            return uri;
+        }
+        return getUriWithoutUserId(uri);
     }
 
     /** @hide */