summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-20 15:20:04 -0700
committerJeff Sharkey <jsharkey@android.com>2013-08-23 11:27:25 -0700
commitbd3b902567b09379e1b62c60b3319ad82102efad (patch)
treec179f95abfe89e2029e099c1dba50dcefc6dfe7b /core
parentb4f6a01d51612b9e50dffd95905e9c9add79b5d8 (diff)
downloadframeworks_base-bd3b902567b09379e1b62c60b3319ad82102efad.zip
frameworks_base-bd3b902567b09379e1b62c60b3319ad82102efad.tar.gz
frameworks_base-bd3b902567b09379e1b62c60b3319ad82102efad.tar.bz2
Add CancellationSignal support to file operations.
Since ContentProvider file operations can end up doing substantial network I/O before returning the file, allow clients to cancel their file requests with CancellationSignal. Ideally this would only be needed for openFile(), but ContentResolver heavily relies on openAssetFile() and openTypedAssetFile() for common cases. Also improve documentation to mention reliable ParcelFileDescriptors and encourage developers to move away from "rw" combination modes, since they restrict provider flexibility. Mention more about places where pipes or socket pairs could be returned. Improve DocumentsContract documentation. Bug: 10329944 Change-Id: I49b2825ea433eb051624c4da3b77612fe3ffc99c
Diffstat (limited to 'core')
-rw-r--r--core/java/android/content/ContentProvider.java227
-rw-r--r--core/java/android/content/ContentProviderClient.java63
-rw-r--r--core/java/android/content/ContentProviderNative.java30
-rw-r--r--core/java/android/content/ContentResolver.java199
-rw-r--r--core/java/android/content/IContentProvider.java8
-rw-r--r--core/java/android/content/Intent.java19
-rw-r--r--core/java/android/provider/DocumentsContract.java141
7 files changed, 600 insertions, 87 deletions
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index cf627d7..b9121c7 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -16,11 +16,9 @@
package android.content;
-import static android.content.pm.PackageManager.GET_PROVIDERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.app.AppOpsManager;
-import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.res.AssetFileDescriptor;
@@ -261,17 +259,21 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode)
+ public ParcelFileDescriptor openFile(
+ String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
- return ContentProvider.this.openFile(uri, mode);
+ return ContentProvider.this.openFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
}
@Override
- public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode)
+ public AssetFileDescriptor openAssetFile(
+ String callingPkg, Uri uri, String mode, ICancellationSignal cancellationSignal)
throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, mode);
- return ContentProvider.this.openAssetFile(uri, mode);
+ return ContentProvider.this.openAssetFile(
+ uri, mode, CancellationSignal.fromTransport(cancellationSignal));
}
@Override
@@ -286,9 +288,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
@Override
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
- Bundle opts) throws FileNotFoundException {
+ Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
enforceFilePermission(callingPkg, uri, "r");
- return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
+ return ContentProvider.this.openTypedAssetFile(
+ uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
}
@Override
@@ -874,6 +877,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* <p>The returned ParcelFileDescriptor is owned by the caller, so it is
* their responsibility to close it when done. That is, the implementation
* of this method should create a new ParcelFileDescriptor for each call.
+ * <p>
+ * If opened with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor can be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" or "rwt" modes implies a file on disk that
+ * supports seeking.
+ * <p>
+ * If you need to detect when the returned ParcelFileDescriptor has been
+ * closed, or if the remote process has crashed or encountered some other
+ * error, you can use {@link ParcelFileDescriptor#open(File, int,
+ * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)},
+ * {@link ParcelFileDescriptor#createReliablePipe()}, or
+ * {@link ParcelFileDescriptor#createReliableSocketPair()}.
*
* <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
@@ -911,6 +926,74 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * Override this to handle requests to open a file blob.
+ * The default implementation always throws {@link FileNotFoundException}.
+ * 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>This method returns a ParcelFileDescriptor, which is returned directly
+ * to the caller. This way large data (such as images and documents) can be
+ * returned without copying the content.
+ *
+ * <p>The returned ParcelFileDescriptor is owned by the caller, so it is
+ * their responsibility to close it when done. That is, the implementation
+ * of this method should create a new ParcelFileDescriptor for each call.
+ * <p>
+ * If opened with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor can be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" or "rwt" modes implies a file on disk that
+ * supports seeking.
+ * <p>
+ * If you need to detect when the returned ParcelFileDescriptor has been
+ * closed, or if the remote process has crashed or encountered some other
+ * error, you can use {@link ParcelFileDescriptor#open(File, int,
+ * android.os.Handler, android.os.ParcelFileDescriptor.OnCloseListener)},
+ * {@link ParcelFileDescriptor#createReliablePipe()}, or
+ * {@link ParcelFileDescriptor#createReliableSocketPair()}.
+ *
+ * <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
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access, "rw" for read and write access, or
+ * "rwt" for read and write access that truncates any existing
+ * file.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new ParcelFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * 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 #openAssetFile(Uri, String)
+ * @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
+ */
+ public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ return openFile(uri, mode);
+ }
+
+ /**
* This is like {@link #openFile}, but can be implemented by providers
* that need to be able to return sub-sections of files, often assets
* inside of their .apk.
@@ -924,11 +1007,14 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* {@link ContentResolver#openInputStream ContentResolver.openInputStream}
* or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
* methods.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
*
* <p class="note">If you are implementing this to return a full file, you
* should create the AssetFileDescriptor with
* {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
- * applications that can not handle sub-sections of files.</p>
+ * applications that cannot handle sub-sections of files.</p>
*
* <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
@@ -965,6 +1051,68 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * This is like {@link #openFile}, but can be implemented by providers
+ * that need to be able to return sub-sections of files, often assets
+ * inside of their .apk.
+ * 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>If you implement this, your clients must be able to deal with such
+ * file slices, either directly with
+ * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level
+ * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
+ * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
+ * methods.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">If you are implementing this to return a full file, you
+ * should create the AssetFileDescriptor with
+ * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
+ * applications that cannot handle sub-sections of files.</p>
+ *
+ * <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
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p>
+ *
+ * @param uri The URI whose file is to be opened.
+ * @param mode Access mode for the file. May be "r" for read-only access,
+ * "w" for write-only access (erasing whatever data is currently in
+ * the file), "wa" for write-only access to append to any existing data,
+ * "rw" for read and write access on any existing data, and "rwt" for read
+ * and write access that truncates any existing file.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new AssetFileDescriptor which you can use to access
+ * the file.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * 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, CancellationSignal signal)
+ throws FileNotFoundException {
+ return openAssetFile(uri, mode);
+ }
+
+ /**
* Convenience for subclasses that wish to implement {@link #openFile}
* by looking up a column named "_data" at the given URI.
*
@@ -1041,6 +1189,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* <p>See {@link ClipData} for examples of the use and implementation
* of this method.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
*
* <p class="note">For better interoperability with other applications, it is recommended
* that for any URIs that can be opened, you also support queries on them
@@ -1086,6 +1237,64 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
}
+
+ /**
+ * Called by a client to open a read-only stream containing data of a
+ * particular MIME type. This is like {@link #openAssetFile(Uri, String)},
+ * except the file can only be read-only and the content provider may
+ * perform data conversions to generate data of the desired type.
+ *
+ * <p>The default implementation compares the given mimeType against the
+ * result of {@link #getType(Uri)} and, if they match, simply calls
+ * {@link #openAssetFile(Uri, String)}.
+ *
+ * <p>See {@link ClipData} for examples of the use and implementation
+ * of this method.
+ * <p>
+ * The returned AssetFileDescriptor can be a pipe or socket pair to enable
+ * streaming of data.
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as *\/*, if the caller does not have specific type
+ * requirements; in this case the content provider will pick its best
+ * type matching the pattern.
+ * @param opts Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ * @param signal A signal to cancel the operation in progress, or
+ * {@code null} if none. For example, if you are downloading a
+ * file from the network to service a "rw" mode request, you
+ * should periodically call
+ * {@link CancellationSignal#throwIfCanceled()} to check whether
+ * the client has canceled the request and abort the download.
+ *
+ * @return Returns a new AssetFileDescriptor from which the client can
+ * read data of the desired type.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * 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 data.
+ * @throws IllegalArgumentException Throws IllegalArgumentException if the
+ * content provider does not support the requested MIME type.
+ *
+ * @see #getStreamTypes(Uri, String)
+ * @see #openAssetFile(Uri, String)
+ * @see ClipDescription#compareMimeTypes(String, String)
+ */
+ public AssetFileDescriptor openTypedAssetFile(
+ Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+ throws FileNotFoundException {
+ return openTypedAssetFile(uri, mimeTypeFilter, opts);
+ }
+
/**
* Interface to write a stream of data to a pipe. Use with
* {@link ContentProvider#openPipeHelper}.
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 8dffac7..024a521 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -63,14 +63,7 @@ public class ContentProviderClient {
/** See {@link ContentProvider#query ContentProvider.query} */
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
- try {
- return query(url, projection, selection, selectionArgs, sortOrder, null);
- } catch (DeadObjectException e) {
- if (!mStable) {
- mContentResolver.unstableProviderDied(mContentProvider);
- }
- throw e;
- }
+ return query(url, projection, selection, selectionArgs, sortOrder, null);
}
/** See {@link ContentProvider#query ContentProvider.query} */
@@ -177,8 +170,25 @@ public class ContentProviderClient {
*/
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
+ return openFile(url, mode, null);
+ }
+
+ /**
+ * See {@link ContentProvider#openFile ContentProvider.openFile}. Note that
+ * this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openFileDescriptor
+ * ContentResolver.openFileDescriptor} API instead.
+ */
+ public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
try {
- return mContentProvider.openFile(mPackageName, url, mode);
+ return mContentProvider.openFile(mPackageName, url, mode, remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -196,8 +206,25 @@ public class ContentProviderClient {
*/
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
+ return openAssetFile(url, mode, null);
+ }
+
+ /**
+ * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
+ * Note that this <em>does not</em>
+ * take care of non-content: URIs such as file:. It is strongly recommended
+ * you use the {@link ContentResolver#openAssetFileDescriptor
+ * ContentResolver.openAssetFileDescriptor} API instead.
+ */
+ public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal)
+ throws RemoteException, FileNotFoundException {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
try {
- return mContentProvider.openAssetFile(mPackageName, url, mode);
+ return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -208,10 +235,22 @@ public class ContentProviderClient {
/** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
- String mimeType, Bundle opts)
+ String mimeType, Bundle opts) throws RemoteException, FileNotFoundException {
+ return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
+ }
+
+ /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
+ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+ String mimeType, Bundle opts, CancellationSignal signal)
throws RemoteException, FileNotFoundException {
+ ICancellationSignal remoteSignal = null;
+ if (signal != null) {
+ remoteSignal = mContentProvider.createCancellationSignal();
+ signal.setRemote(remoteSignal);
+ }
try {
- return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
+ return mContentProvider.openTypedAssetFile(
+ mPackageName, uri, mimeType, opts, remoteSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 6f822c1..744e68c 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -18,22 +18,20 @@ package android.content;
import android.content.res.AssetFileDescriptor;
import android.database.BulkCursorDescriptor;
-import android.database.BulkCursorNative;
import android.database.BulkCursorToCursorAdaptor;
import android.database.Cursor;
import android.database.CursorToBulkCursorAdaptor;
import android.database.DatabaseUtils;
-import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.RemoteException;
import java.io.FileNotFoundException;
import java.util.ArrayList;
@@ -227,9 +225,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
ParcelFileDescriptor fd;
- fd = openFile(callingPkg, url, mode);
+ fd = openFile(callingPkg, url, mode, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -247,9 +247,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
AssetFileDescriptor fd;
- fd = openAssetFile(callingPkg, url, mode);
+ fd = openAssetFile(callingPkg, url, mode, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -296,9 +298,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
Uri url = Uri.CREATOR.createFromParcel(data);
String mimeType = data.readString();
Bundle opts = data.readBundle();
+ ICancellationSignal signal = ICancellationSignal.Stub.asInterface(
+ data.readStrongBinder());
AssetFileDescriptor fd;
- fd = openTypedAssetFile(callingPkg, url, mimeType, opts);
+ fd = openTypedAssetFile(callingPkg, url, mimeType, opts, signal);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -538,7 +542,9 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
+ @Override
+ public ParcelFileDescriptor openFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -548,6 +554,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
@@ -561,7 +568,9 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
+ @Override
+ public AssetFileDescriptor openAssetFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -571,6 +580,7 @@ final class ContentProviderProxy implements IContentProvider
data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
@@ -629,8 +639,9 @@ final class ContentProviderProxy implements IContentProvider
}
}
+ @Override
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
- Bundle opts) throws RemoteException, FileNotFoundException {
+ Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
@@ -640,6 +651,7 @@ final class ContentProviderProxy implements IContentProvider
url.writeToParcel(data, 0);
data.writeString(mimeType);
data.writeBundle(opts);
+ data.writeStrongBinder(signal != null ? signal.asBinder() : null);
mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 5cabfee..1379d1b 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -55,7 +55,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;
-
/**
* This class provides applications access to the content model.
*
@@ -328,7 +327,7 @@ public abstract class ContentResolver {
* content URL can be returned when opened as as stream with
* {@link #openTypedAssetFileDescriptor}. Note that the types here are
* not necessarily a superset of the type returned by {@link #getType} --
- * many content providers can not return a raw stream for the structured
+ * many content providers cannot return a raw stream for the structured
* data that they contain.
*
* @param url A Uri identifying content (either a list or specific type),
@@ -485,6 +484,9 @@ public abstract class ContentResolver {
if (qCursor != null) {
qCursor.close();
}
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
@@ -531,7 +533,7 @@ public abstract class ContentResolver {
// with sufficient testing.
return new FileInputStream(uri.getPath());
} else {
- AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r");
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null);
try {
return fd != null ? fd.createInputStream() : null;
} catch (IOException e) {
@@ -571,7 +573,7 @@ public abstract class ContentResolver {
*/
public final OutputStream openOutputStream(Uri uri, String mode)
throws FileNotFoundException {
- AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode);
+ AssetFileDescriptor fd = openAssetFileDescriptor(uri, mode, null);
try {
return fd != null ? fd.createOutputStream() : null;
} catch (IOException e) {
@@ -597,10 +599,64 @@ public abstract class ContentResolver {
*
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
+ * <p>
+ * If opening with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor could be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" mode implies a file on disk that supports
+ * seeking. If possible, always use an exclusive mode to give the underlying
+ * {@link ContentProvider} the most flexibility.
+ * <p>
+ * If you are writing a file, and need to communicate an error to the
+ * provider, use {@link ParcelFileDescriptor#closeWithError(String)}.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The file mode to use, as per {@link ContentProvider#openFile
+ * ContentProvider.openFile}.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. You
+ * own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException if no
+ * file exists under the URI or the mode is invalid.
+ * @see #openAssetFileDescriptor(Uri, String)
+ */
+ public final ParcelFileDescriptor openFileDescriptor(Uri uri, String mode)
+ throws FileNotFoundException {
+ return openFileDescriptor(uri, mode, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
+ * underlying {@link ContentProvider#openFile}
+ * ContentProvider.openFile()} method, so will <em>not</em> work with
+ * providers that return sub-sections of files. If at all possible,
+ * you should use {@link #openAssetFileDescriptor(Uri, String)}. You
+ * will receive a FileNotFoundException exception if the provider returns a
+ * sub-section of a file.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ *
+ * <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
+ * on these schemes.
+ * <p>
+ * If opening with the exclusive "r" or "w" modes, the returned
+ * ParcelFileDescriptor could be a pipe or socket pair to enable streaming
+ * of data. Opening with the "rw" mode implies a file on disk that supports
+ * seeking. If possible, always use an exclusive mode to give the underlying
+ * {@link ContentProvider} the most flexibility.
+ * <p>
+ * If you are writing a file, and need to communicate an error to the
+ * provider, use {@link ParcelFileDescriptor#closeWithError(String)}.
*
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openFile
* ContentProvider.openFile}.
+ * @param signal A signal to cancel the operation in progress, or null if
+ * none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
* @return Returns a new ParcelFileDescriptor pointing to the file. You
* own this descriptor and are responsible for closing it when done.
* @throws FileNotFoundException Throws FileNotFoundException if no
@@ -608,8 +664,8 @@ public abstract class ContentResolver {
* @see #openAssetFileDescriptor(Uri, String)
*/
public final ParcelFileDescriptor openFileDescriptor(Uri uri,
- String mode) throws FileNotFoundException {
- AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode);
+ String mode, CancellationSignal cancellationSignal) throws FileNotFoundException {
+ AssetFileDescriptor afd = openAssetFileDescriptor(uri, mode, cancellationSignal);
if (afd == null) {
return null;
}
@@ -677,8 +733,64 @@ public abstract class ContentResolver {
* @throws FileNotFoundException Throws FileNotFoundException of no
* file exists under the URI or the mode is invalid.
*/
+ public final AssetFileDescriptor openAssetFileDescriptor(Uri uri, String mode)
+ throws FileNotFoundException {
+ return openAssetFileDescriptor(uri, mode, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access data under a URI. This
+ * interacts with the underlying {@link ContentProvider#openAssetFile}
+ * method of the provider associated with the given URI, to retrieve any file stored there.
+ *
+ * <h5>Accepts the following URI schemes:</h5>
+ * <ul>
+ * <li>content ({@link #SCHEME_CONTENT})</li>
+ * <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
+ * <li>file ({@link #SCHEME_FILE})</li>
+ * </ul>
+ * <h5>The android.resource ({@link #SCHEME_ANDROID_RESOURCE}) Scheme</h5>
+ * <p>
+ * A Uri object can be used to reference a resource in an APK file. The
+ * Uri should be one of the following formats:
+ * <ul>
+ * <li><code>android.resource://package_name/id_number</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>id_number</code> is the int form of the ID.<br/>
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/" + R.raw.my_resource");</pre>
+ * </li>
+ * <li><code>android.resource://package_name/type/name</code><br/>
+ * <code>package_name</code> is your package name as listed in your AndroidManifest.xml.
+ * For example <code>com.example.myapp</code><br/>
+ * <code>type</code> is the string form of the resource type. For example, <code>raw</code>
+ * or <code>drawable</code>.
+ * <code>name</code> is the string form of the resource name. That is, whatever the file
+ * name was in your res directory, without the type extension.
+ * The easiest way to construct this form is
+ * <pre>Uri uri = Uri.parse("android.resource://com.example.myapp/raw/my_resource");</pre>
+ * </li>
+ * </ul>
+ *
+ * <p>Note that if this function is called for read-only input (mode is "r")
+ * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+ * for you with a MIME type of "*\/*". This allows such callers to benefit
+ * from any built-in data conversion that a provider implements.
+ *
+ * @param uri The desired URI to open.
+ * @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
+ * ContentProvider.openAssetFile}.
+ * @param signal A signal to cancel the operation in progress, or null if
+ * none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
+ * @return Returns a new ParcelFileDescriptor pointing to the file. You
+ * own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * file exists under the URI or the mode is invalid.
+ */
public final AssetFileDescriptor openAssetFileDescriptor(Uri uri,
- String mode) throws FileNotFoundException {
+ String mode, CancellationSignal cancellationSignal) throws FileNotFoundException {
String scheme = uri.getScheme();
if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
if (!"r".equals(mode)) {
@@ -696,7 +808,7 @@ public abstract class ContentResolver {
return new AssetFileDescriptor(pfd, 0, -1);
} else {
if ("r".equals(mode)) {
- return openTypedAssetFileDescriptor(uri, "*/*", null);
+ return openTypedAssetFileDescriptor(uri, "*/*", null, cancellationSignal);
} else {
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
@@ -706,8 +818,16 @@ public abstract class ContentResolver {
AssetFileDescriptor fd = null;
try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = unstableProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+
try {
- fd = unstableProvider.openAssetFile(mPackageName, uri, mode);
+ fd = unstableProvider.openAssetFile(
+ mPackageName, uri, mode, remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -721,7 +841,8 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openAssetFile(mPackageName, uri, mode);
+ fd = stableProvider.openAssetFile(
+ mPackageName, uri, mode, remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -749,6 +870,9 @@ public abstract class ContentResolver {
} catch (FileNotFoundException e) {
throw e;
} finally {
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
if (stableProvider != null) {
releaseProvider(stableProvider);
}
@@ -788,8 +912,45 @@ public abstract class ContentResolver {
* @throws FileNotFoundException Throws FileNotFoundException of no
* data of the desired type exists under the URI.
*/
+ public final AssetFileDescriptor openTypedAssetFileDescriptor(
+ Uri uri, String mimeType, Bundle opts) throws FileNotFoundException {
+ return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
+ }
+
+ /**
+ * Open a raw file descriptor to access (potentially type transformed)
+ * data from a "content:" URI. This interacts with the underlying
+ * {@link ContentProvider#openTypedAssetFile} method of the provider
+ * associated with the given URI, to retrieve retrieve any appropriate
+ * data stream for the data stored there.
+ *
+ * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+ * with "content:" URIs, because content providers are the only facility
+ * with an associated MIME type to ensure that the returned data stream
+ * is of the desired type.
+ *
+ * <p>All text/* streams are encoded in UTF-8.
+ *
+ * @param uri The desired URI to open.
+ * @param mimeType The desired MIME type of the returned data. This can
+ * be a pattern such as *\/*, which will allow the content provider to
+ * select a type, though there is no way for you to determine what type
+ * it is returning.
+ * @param opts Additional provider-dependent options.
+ * @param signal A signal to cancel the operation in progress, or null if
+ * none. If the operation is canceled, then
+ * {@link OperationCanceledException} will be thrown.
+ * @return Returns a new ParcelFileDescriptor from which you can read the
+ * data stream from the provider. Note that this may be a pipe, meaning
+ * you can't seek in it. The only seek you should do is if the
+ * AssetFileDescriptor contains an offset, to move to that offset before
+ * reading. You own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * data of the desired type exists under the URI.
+ */
public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
- String mimeType, Bundle opts) throws FileNotFoundException {
+ String mimeType, Bundle opts, CancellationSignal cancellationSignal)
+ throws FileNotFoundException {
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
@@ -798,8 +959,16 @@ public abstract class ContentResolver {
AssetFileDescriptor fd = null;
try {
+ ICancellationSignal remoteCancellationSignal = null;
+ if (cancellationSignal != null) {
+ cancellationSignal.throwIfCanceled();
+ remoteCancellationSignal = unstableProvider.createCancellationSignal();
+ cancellationSignal.setRemote(remoteCancellationSignal);
+ }
+
try {
- fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
+ fd = unstableProvider.openTypedAssetFile(
+ mPackageName, uri, mimeType, opts, remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -813,7 +982,8 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
+ fd = stableProvider.openTypedAssetFile(
+ mPackageName, uri, mimeType, opts, remoteCancellationSignal);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -841,6 +1011,9 @@ public abstract class ContentResolver {
} catch (FileNotFoundException e) {
throw e;
} finally {
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(null);
+ }
if (stableProvider != null) {
releaseProvider(stableProvider);
}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 62b79f0..6ea8876 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -46,9 +46,11 @@ public interface IContentProvider extends IInterface {
throws RemoteException;
public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
- public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
+ public ParcelFileDescriptor openFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
- public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
+ public AssetFileDescriptor openAssetFile(
+ String callingPkg, Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList<ContentProviderOperation> operations)
@@ -60,7 +62,7 @@ public interface IContentProvider extends IInterface {
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
- Bundle opts) throws RemoteException, FileNotFoundException;
+ Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 017ad98..be13620 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -35,6 +35,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
+import android.provider.DocumentsContract;
import android.util.AttributeSet;
import android.util.Log;
@@ -2650,11 +2651,16 @@ public class Intent implements Parcelable, Cloneable {
* multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} to
* indicate this.
* <p>
- * All returned URIs can be opened as a stream with
- * {@link ContentResolver#openInputStream(Uri)}.
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
+ * returned URIs can be opened with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
* Output: The URI of the item that was picked. This must be a content: URI
- * so that any receiver can access it.
+ * so that any receiver can access it. If multiple documents were selected,
+ * they are returned in {@link #getClipData()}.
+ *
+ * @see DocumentsContract
+ * @see DocumentsContract#getOpenDocuments(Context)
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
@@ -2669,11 +2675,14 @@ public class Intent implements Parcelable, Cloneable {
* optionally hint at the MIME type being created by setting
* {@link #setType(String)}.
* <p>
- * All returned URIs can be opened as a stream with
- * {@link ContentResolver#openOutputStream(Uri)}.
+ * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
+ * returned URIs can be opened with
+ * {@link ContentResolver#openFileDescriptor(Uri, String)}.
* <p>
* Output: The URI of the item that was created. This must be a content: URI
* so that any receiver can access it.
+ *
+ * @see DocumentsContract
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index da7647a..0d1a740 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -30,6 +30,8 @@ import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.OnCloseListener;
import android.util.Log;
import com.google.android.collect.Lists;
@@ -41,8 +43,33 @@ import java.io.IOException;
import java.util.List;
/**
- * The contract between a storage backend and the platform. Contains definitions
- * for the supported URIs and columns.
+ * Defines the contract between a documents provider and the platform.
+ * <p>
+ * A document provider is a {@link ContentProvider} that presents a set of
+ * documents in a hierarchical structure. The system provides UI that visualizes
+ * all available document providers, offering users the ability to open existing
+ * documents or create new documents.
+ * <p>
+ * Each provider expresses one or more "roots" which each serve as the top-level
+ * of a tree. For example, a root could represent an account, or a physical
+ * storage device. Under each root, documents are referenced by a unique
+ * {@link DocumentColumns#DOC_ID}, and each root starts at the
+ * {@link Documents#DOC_ID_ROOT} document.
+ * <p>
+ * Documents can be either an openable file (with a specific MIME type), or a
+ * directory containing additional documents (with the
+ * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
+ * capabilities, as described by {@link DocumentColumns#FLAGS}. The same
+ * {@link DocumentColumns#DOC_ID} can be included in multiple directories.
+ * <p>
+ * Document providers must be protected with the
+ * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
+ * only be requested by the system. The system-provided UI then issues narrow
+ * Uri permission grants for individual documents when the user explicitly picks
+ * documents.
+ *
+ * @see Intent#ACTION_OPEN_DOCUMENT
+ * @see Intent#ACTION_CREATE_DOCUMENT
*/
public final class DocumentsContract {
private static final String TAG = "Documents";
@@ -59,6 +86,9 @@ public final class DocumentsContract {
/** {@hide} */
public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED";
+ /**
+ * Constants for individual documents.
+ */
public static class Documents {
private Documents() {
}
@@ -73,7 +103,7 @@ public final class DocumentsContract {
/**
* {@link DocumentColumns#DOC_ID} value representing the root directory of a
- * storage root.
+ * documents root.
*/
public static final String DOC_ID_ROOT = "0";
@@ -144,8 +174,8 @@ public final class DocumentsContract {
/**
* Extra boolean flag included in a directory {@link Cursor#getExtras()}
- * indicating that the backend can provide additional data if requested,
- * such as additional search results.
+ * indicating that the document provider can provide additional data if
+ * requested, such as additional search results.
*/
public static final String EXTRA_HAS_MORE = "has_more";
@@ -170,21 +200,24 @@ public final class DocumentsContract {
private static final String PARAM_LOCAL_ONLY = "localOnly";
/**
- * Build URI representing the roots in a storage backend.
+ * Build Uri representing the roots offered by a document provider.
*/
public static Uri buildRootsUri(String authority) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).appendPath(PATH_ROOTS).build();
}
+ /**
+ * Build Uri representing a specific root offered by a document provider.
+ */
public static Uri buildRootUri(String authority, String rootId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build();
}
/**
- * Build URI representing the given {@link DocumentColumns#DOC_ID} in a
- * storage root.
+ * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
+ * document provider.
*/
public static Uri buildDocumentUri(String authority, String rootId, String docId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
@@ -193,8 +226,8 @@ public final class DocumentsContract {
}
/**
- * Build URI representing the contents of the given directory in a storage
- * backend. The given document must be {@link Documents#MIME_TYPE_DIR}.
+ * Build Uri representing the contents of the given directory in a document
+ * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
*/
public static Uri buildContentsUri(String authority, String rootId, String docId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
@@ -203,8 +236,9 @@ public final class DocumentsContract {
}
/**
- * Build URI representing a search for matching documents under a directory
- * in a storage backend.
+ * Build Uri representing a search for matching documents under a specific
+ * directory in a document provider. The given document must have
+ * {@link Documents#FLAG_SUPPORTS_SEARCH}.
*/
public static Uri buildSearchUri(String authority, String rootId, String docId, String query) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
@@ -212,20 +246,36 @@ public final class DocumentsContract {
.appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
}
+ /**
+ * Convenience method for {@link #buildDocumentUri(String, String, String)},
+ * extracting authority and root from the given Uri.
+ */
public static Uri buildDocumentUri(Uri relatedUri, String docId) {
return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId);
}
+ /**
+ * Convenience method for {@link #buildContentsUri(String, String, String)},
+ * extracting authority and root from the given Uri.
+ */
public static Uri buildContentsUri(Uri relatedUri) {
return buildContentsUri(
relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri));
}
+ /**
+ * Convenience method for
+ * {@link #buildSearchUri(String, String, String, String)}, extracting
+ * authority and root from the given Uri.
+ */
public static Uri buildSearchUri(Uri relatedUri, String query) {
return buildSearchUri(
relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query);
}
+ /**
+ * Extract the {@link RootColumns#ROOT_ID} from the given Uri.
+ */
public static String getRootId(Uri documentUri) {
final List<String> paths = documentUri.getPathSegments();
if (paths.size() < 2) {
@@ -237,6 +287,9 @@ public final class DocumentsContract {
return paths.get(1);
}
+ /**
+ * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
+ */
public static String getDocId(Uri documentUri) {
final List<String> paths = documentUri.getPathSegments();
if (paths.size() < 4) {
@@ -252,15 +305,17 @@ public final class DocumentsContract {
}
/**
- * Return requested search query from the given Uri.
+ * Return requested search query from the given Uri, as constructed by
+ * {@link #buildSearchUri(String, String, String, String)}.
*/
public static String getSearchQuery(Uri documentUri) {
return documentUri.getQueryParameter(PARAM_QUERY);
}
/**
- * Mark the given Uri to indicate that only locally-available contents
- * should be returned.
+ * Mark the given Uri to indicate that only locally-available data should be
+ * returned. That is, no network connections should be initiated to provide
+ * the metadata or content.
*/
public static Uri setLocalOnly(Uri documentUri) {
return documentUri.buildUpon()
@@ -268,20 +323,21 @@ public final class DocumentsContract {
}
/**
- * Return if the given Uri is requesting that only locally-available content
- * be returned. That is, no network connections should be initiated to
- * provide the metadata or content.
+ * Return if the given Uri is requesting that only locally-available data be
+ * returned. That is, no network connections should be initiated to provide
+ * the metadata or content.
*/
public static boolean isLocalOnly(Uri documentUri) {
return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false);
}
/**
- * These are standard columns for document URIs. Storage backend providers
- * <em>must</em> support at least these columns when queried.
+ * Standard columns for document queries. Document providers <em>must</em>
+ * support at least these columns when queried.
*
- * @see Intent#ACTION_OPEN_DOCUMENT
- * @see Intent#ACTION_CREATE_DOCUMENT
+ * @see DocumentsContract#buildDocumentUri(String, String, String)
+ * @see DocumentsContract#buildContentsUri(String, String, String)
+ * @see DocumentsContract#buildSearchUri(String, String, String, String)
*/
public interface DocumentColumns extends OpenableColumns {
/**
@@ -296,8 +352,8 @@ public final class DocumentsContract {
/**
* MIME type of a document, matching the value returned by
* {@link ContentResolver#getType(android.net.Uri)}. This field must be
- * provided when a new document is created, but after that the field is
- * read-only.
+ * provided when a new document is created. This field is read-only to
+ * document clients.
* <p>
* Type: STRING
*
@@ -308,7 +364,9 @@ public final class DocumentsContract {
/**
* Timestamp when a document was last modified, in milliseconds since
* January 1, 1970 00:00:00.0 UTC. This field is read-only to document
- * clients.
+ * clients. Document providers can update this field using events from
+ * {@link OnCloseListener} or other reliable
+ * {@link ParcelFileDescriptor} transport.
* <p>
* Type: INTEGER (long)
*
@@ -325,13 +383,17 @@ public final class DocumentsContract {
public static final String FLAGS = "flags";
/**
- * Summary for this document, or {@code null} to omit.
+ * Summary for this document, or {@code null} to omit. This field is
+ * read-only to document clients.
* <p>
* Type: STRING
*/
public static final String SUMMARY = "summary";
}
+ /**
+ * Constants for individual document roots.
+ */
public static class Roots {
private Roots() {
}
@@ -340,7 +402,8 @@ public final class DocumentsContract {
public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root";
/**
- * Root that represents a cloud-based storage service.
+ * Root that represents a storage service, such as a cloud-based
+ * service.
*
* @see RootColumns#ROOT_TYPE
*/
@@ -371,15 +434,17 @@ public final class DocumentsContract {
}
/**
- * These are standard columns for the roots URI.
+ * Standard columns for document root queries.
*
* @see DocumentsContract#buildRootsUri(String)
+ * @see DocumentsContract#buildRootUri(String, String)
*/
public interface RootColumns {
public static final String ROOT_ID = "root_id";
/**
- * Storage root type, use for clustering.
+ * Storage root type, use for clustering. This field is read-only to
+ * document clients.
* <p>
* Type: INTEGER (int)
*
@@ -389,8 +454,9 @@ public final class DocumentsContract {
public static final String ROOT_TYPE = "root_type";
/**
- * Icon resource ID for this storage root, or {@code 0} to use the
- * default {@link ProviderInfo#icon}.
+ * Icon resource ID for this storage root, or {@code null} to use the
+ * default {@link ProviderInfo#icon}. This field is read-only to
+ * document clients.
* <p>
* Type: INTEGER (int)
*/
@@ -398,22 +464,25 @@ public final class DocumentsContract {
/**
* Title for this storage root, or {@code null} to use the default
- * {@link ProviderInfo#labelRes}.
+ * {@link ProviderInfo#labelRes}. This field is read-only to document
+ * clients.
* <p>
* Type: STRING
*/
public static final String TITLE = "title";
/**
- * Summary for this storage root, or {@code null} to omit.
+ * Summary for this storage root, or {@code null} to omit. This field is
+ * read-only to document clients.
* <p>
* Type: STRING
*/
public static final String SUMMARY = "summary";
/**
- * Number of free bytes of available in this storage root, or -1 if
- * unknown or unbounded.
+ * Number of free bytes of available in this storage root, or
+ * {@code null} if unknown or unbounded. This field is read-only to
+ * document clients.
* <p>
* Type: INTEGER (long)
*/
@@ -452,7 +521,7 @@ public final class DocumentsContract {
/**
* Return thumbnail representing the document at the given URI. Callers are
- * responsible for their own caching. Given document must have
+ * responsible for their own in-memory caching. Given document must have
* {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
*
* @return decoded thumbnail, or {@code null} if problem was encountered.