Refactoring of DocumentsContract.
Combines related columns and constants onto the same class so they are easier to discover. Move back to surfacing roots with columns so they are consistent with documents. Advanced roots are represented with a flag instead of distinct types. Flags to indicate supporting of well-known media types, instead of arbitrary an MIME filter. Reintroduce well-formed rootId to support recents. Always use the expanded version of "documents" in constants, methods, and argument names. Refactor DocumentProvider method names to clearly distinguish if a single item or multiple could be returned, and of which type. Add documentation to clearly define which methods have already been overridden. Bug: 10567506, 10567557 Change-Id: I981f26ab82f2b520a19aa1ce66f659de50d7fac0
This commit is contained in:
@ -20781,67 +20781,69 @@ package android.provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class DocumentsContract {
|
public final class DocumentsContract {
|
||||||
|
method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String);
|
||||||
method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
|
method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
|
||||||
method public static java.lang.String getDocId(android.net.Uri);
|
method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String);
|
||||||
|
method public static android.net.Uri buildRootsUri(java.lang.String);
|
||||||
|
method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String);
|
||||||
|
method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String);
|
||||||
|
method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri);
|
||||||
|
method public static java.lang.String getDocumentId(android.net.Uri);
|
||||||
|
method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal);
|
||||||
method public static android.net.Uri[] getOpenDocuments(android.content.Context);
|
method public static android.net.Uri[] getOpenDocuments(android.content.Context);
|
||||||
|
method public static java.lang.String getRootId(android.net.Uri);
|
||||||
|
method public static java.lang.String getSearchDocumentsQuery(android.net.Uri);
|
||||||
field public static final java.lang.String EXTRA_ERROR = "error";
|
field public static final java.lang.String EXTRA_ERROR = "error";
|
||||||
field public static final java.lang.String EXTRA_INFO = "info";
|
field public static final java.lang.String EXTRA_INFO = "info";
|
||||||
field public static final java.lang.String EXTRA_LOADING = "loading";
|
field public static final java.lang.String EXTRA_LOADING = "loading";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
|
public static final class DocumentsContract.Document {
|
||||||
field public static final java.lang.String DOC_ID = "doc_id";
|
field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name";
|
||||||
field public static final java.lang.String FLAGS = "flags";
|
field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
|
||||||
field public static final java.lang.String ICON = "icon";
|
field public static final java.lang.String COLUMN_FLAGS = "flags";
|
||||||
field public static final java.lang.String LAST_MODIFIED = "last_modified";
|
field public static final java.lang.String COLUMN_ICON = "icon";
|
||||||
field public static final java.lang.String MIME_TYPE = "mime_type";
|
field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified";
|
||||||
field public static final java.lang.String SUMMARY = "summary";
|
field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
|
||||||
|
field public static final java.lang.String COLUMN_SIZE = "_size";
|
||||||
|
field public static final java.lang.String COLUMN_SUMMARY = "summary";
|
||||||
|
field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20
|
||||||
|
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
|
||||||
|
field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10
|
||||||
|
field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
|
||||||
|
field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
|
||||||
|
field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
|
||||||
|
field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class DocumentsContract.DocumentRoot implements android.os.Parcelable {
|
public static final class DocumentsContract.Root {
|
||||||
ctor public DocumentsContract.DocumentRoot();
|
field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes";
|
||||||
method public int describeContents();
|
field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
|
||||||
method public void writeToParcel(android.os.Parcel, int);
|
field public static final java.lang.String COLUMN_FLAGS = "flags";
|
||||||
field public static final android.os.Parcelable.Creator CREATOR;
|
field public static final java.lang.String COLUMN_ICON = "icon";
|
||||||
|
field public static final java.lang.String COLUMN_ROOT_ID = "root_id";
|
||||||
|
field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type";
|
||||||
|
field public static final java.lang.String COLUMN_SUMMARY = "summary";
|
||||||
|
field public static final java.lang.String COLUMN_TITLE = "title";
|
||||||
|
field public static final int FLAG_ADVANCED = 4; // 0x4
|
||||||
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
|
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
|
||||||
|
field public static final int FLAG_PROVIDES_AUDIO = 8; // 0x8
|
||||||
|
field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20
|
||||||
|
field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10
|
||||||
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
|
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
|
||||||
field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
|
field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
|
||||||
field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4
|
|
||||||
field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
|
field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
|
||||||
field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
|
field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
|
||||||
field public long availableBytes;
|
|
||||||
field public java.lang.String docId;
|
|
||||||
field public int flags;
|
|
||||||
field public int icon;
|
|
||||||
field public java.lang.String[] mimeTypes;
|
|
||||||
field public java.lang.String recentDocId;
|
|
||||||
field public int rootType;
|
|
||||||
field public java.lang.String summary;
|
|
||||||
field public java.lang.String title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class DocumentsContract.Documents {
|
|
||||||
field public static final int FLAG_PREFERS_GRID = 64; // 0x40
|
|
||||||
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
|
|
||||||
field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
|
|
||||||
field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2
|
|
||||||
field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
|
|
||||||
field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8
|
|
||||||
field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20
|
|
||||||
field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.doc/dir";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DocumentsProvider extends android.content.ContentProvider {
|
public abstract class DocumentsProvider extends android.content.ContentProvider {
|
||||||
ctor public DocumentsProvider();
|
ctor public DocumentsProvider();
|
||||||
method public final android.os.Bundle callFromPackage(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle);
|
|
||||||
method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
|
method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
|
||||||
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
|
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
|
||||||
method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
|
method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException;
|
||||||
method public abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots();
|
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
|
||||||
method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException;
|
|
||||||
method public final java.lang.String getType(android.net.Uri);
|
method public final java.lang.String getType(android.net.Uri);
|
||||||
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
|
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
|
||||||
method public void notifyDocumentRootsChanged();
|
|
||||||
method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||||
method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||||
method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
|
method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
|
||||||
@ -20849,10 +20851,11 @@ package android.provider {
|
|||||||
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
|
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException;
|
||||||
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
|
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
|
||||||
method public abstract android.database.Cursor queryDocument(java.lang.String) throws java.io.FileNotFoundException;
|
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
|
||||||
method public abstract android.database.Cursor queryDocumentChildren(java.lang.String) throws java.io.FileNotFoundException;
|
method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||||
method public android.database.Cursor querySearch(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
|
method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||||
method public void renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException;
|
method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
|
||||||
|
method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||||
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
|
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ package android.provider;
|
|||||||
import static android.net.TrafficStats.KB_IN_BYTES;
|
import static android.net.TrafficStats.KB_IN_BYTES;
|
||||||
import static libcore.io.OsConstants.SEEK_SET;
|
import static libcore.io.OsConstants.SEEK_SET;
|
||||||
|
|
||||||
import android.content.ContentProviderClient;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -30,16 +29,13 @@ import android.database.Cursor;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.CancellationSignal;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.ParcelFileDescriptor.OnCloseListener;
|
import android.os.ParcelFileDescriptor.OnCloseListener;
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.util.Preconditions;
|
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
import libcore.io.ErrnoException;
|
import libcore.io.ErrnoException;
|
||||||
@ -62,9 +58,12 @@ import java.util.List;
|
|||||||
public final class DocumentsContract {
|
public final class DocumentsContract {
|
||||||
private static final String TAG = "Documents";
|
private static final String TAG = "Documents";
|
||||||
|
|
||||||
// content://com.example/docs/12/
|
// content://com.example/root/
|
||||||
// content://com.example/docs/12/children/
|
// content://com.example/root/sdcard/
|
||||||
// content://com.example/docs/12/search/?query=pony
|
// content://com.example/root/sdcard/recent/
|
||||||
|
// content://com.example/document/12/
|
||||||
|
// content://com.example/document/12/children/
|
||||||
|
// content://com.example/document/12/search/?query=pony
|
||||||
|
|
||||||
private DocumentsContract() {
|
private DocumentsContract() {
|
||||||
}
|
}
|
||||||
@ -75,207 +74,69 @@ public final class DocumentsContract {
|
|||||||
/** {@hide} */
|
/** {@hide} */
|
||||||
public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
|
public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String
|
|
||||||
ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants for individual documents.
|
* Constants related to a document, including {@link Cursor} columns names
|
||||||
|
* and flags.
|
||||||
|
* <p>
|
||||||
|
* A document can be either an openable file (with a specific MIME type), or
|
||||||
|
* a directory containing additional documents (with the
|
||||||
|
* {@link #MIME_TYPE_DIR} MIME type).
|
||||||
|
* <p>
|
||||||
|
* All columns are <em>read-only</em> to client applications.
|
||||||
*/
|
*/
|
||||||
public final static class Documents {
|
public final static class Document {
|
||||||
private Documents() {
|
private Document() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIME type of a document which is a directory that may contain additional
|
* Unique ID of a document. This ID is both provided by and interpreted
|
||||||
* documents.
|
* by a {@link DocumentsProvider}, and should be treated as an opaque
|
||||||
*/
|
* value by client applications.
|
||||||
public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
|
* <p>
|
||||||
|
* Each document must have a unique ID within a provider, but that
|
||||||
/**
|
* single document may be included as a child of multiple directories.
|
||||||
* Flag indicating that a document is a directory that supports creation of
|
* <p>
|
||||||
* new files within it.
|
* A provider must always return durable IDs, since they will be used to
|
||||||
*
|
* issue long-term Uri permission grants when an application interacts
|
||||||
* @see DocumentColumns#FLAGS
|
* with {@link Intent#ACTION_OPEN_DOCUMENT} and
|
||||||
*/
|
* {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||||
public static final int FLAG_SUPPORTS_CREATE = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document is renamable.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document is deletable.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document can be represented as a thumbnail.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document is a directory that supports search.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document supports writing.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag indicating that a document is a directory that prefers its contents
|
|
||||||
* be shown in a larger format grid. Usually suitable when a directory
|
|
||||||
* contains mostly pictures.
|
|
||||||
*
|
|
||||||
* @see DocumentColumns#FLAGS
|
|
||||||
*/
|
|
||||||
public static final int FLAG_PREFERS_GRID = 1 << 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra boolean flag included in a directory {@link Cursor#getExtras()}
|
|
||||||
* indicating that a document provider is still loading data. For example, a
|
|
||||||
* provider has returned some results, but is still waiting on an
|
|
||||||
* outstanding network request.
|
|
||||||
*
|
|
||||||
* @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
|
|
||||||
* boolean)
|
|
||||||
*/
|
|
||||||
public static final String EXTRA_LOADING = "loading";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra string included in a directory {@link Cursor#getExtras()}
|
|
||||||
* providing an informational message that should be shown to a user. For
|
|
||||||
* example, a provider may wish to indicate that not all documents are
|
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
public static final String EXTRA_INFO = "info";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra string included in a directory {@link Cursor#getExtras()} providing
|
|
||||||
* an error message that should be shown to a user. For example, a provider
|
|
||||||
* may wish to indicate that a network error occurred. The user may choose
|
|
||||||
* to retry, resulting in a new query.
|
|
||||||
*/
|
|
||||||
public static final String EXTRA_ERROR = "error";
|
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String METHOD_GET_ROOTS = "android:getRoots";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
|
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String EXTRA_AUTHORITY = "authority";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String EXTRA_PACKAGE_NAME = "packageName";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String EXTRA_URI = "uri";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String EXTRA_ROOTS = "roots";
|
|
||||||
/** {@hide} */
|
|
||||||
public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
|
|
||||||
|
|
||||||
private static final String PATH_DOCS = "docs";
|
|
||||||
private static final String PATH_CHILDREN = "children";
|
|
||||||
private static final String PATH_SEARCH = "search";
|
|
||||||
|
|
||||||
private static final String PARAM_QUERY = "query";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
|
|
||||||
* document provider.
|
|
||||||
*/
|
|
||||||
public static Uri buildDocumentUri(String authority, String docId) {
|
|
||||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
|
||||||
.authority(authority).appendPath(PATH_DOCS).appendPath(docId).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build Uri representing the contents of the given directory in a document
|
|
||||||
* provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
|
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public static Uri buildChildrenUri(String authority, String docId) {
|
|
||||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
|
|
||||||
.appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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}.
|
|
||||||
*
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public static Uri buildSearchUri(String authority, String docId, String query) {
|
|
||||||
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
|
|
||||||
.appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH)
|
|
||||||
.appendQueryParameter(PARAM_QUERY, query).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() < 2) {
|
|
||||||
throw new IllegalArgumentException("Not a document: " + documentUri);
|
|
||||||
}
|
|
||||||
if (!PATH_DOCS.equals(paths.get(0))) {
|
|
||||||
throw new IllegalArgumentException("Not a document: " + documentUri);
|
|
||||||
}
|
|
||||||
return paths.get(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public static String getSearchQuery(Uri documentUri) {
|
|
||||||
return documentUri.getQueryParameter(PARAM_QUERY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard columns for document queries. Document providers <em>must</em>
|
|
||||||
* support at least these columns when queried.
|
|
||||||
*/
|
|
||||||
public interface DocumentColumns extends OpenableColumns {
|
|
||||||
/**
|
|
||||||
* Unique ID for a document. Values <em>must</em> never change once
|
|
||||||
* returned, since they may used for long-term Uri permission grants.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Type: STRING
|
* Type: STRING
|
||||||
*/
|
*/
|
||||||
public static final String DOC_ID = "doc_id";
|
public static final String COLUMN_DOCUMENT_ID = "document_id";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIME type of a document.
|
* Concrete MIME type of a document. For example, "image/png" or
|
||||||
|
* "application/pdf" for openable files. A document can also be a
|
||||||
|
* directory containing additional documents, which is represented with
|
||||||
|
* the {@link #MIME_TYPE_DIR} MIME type.
|
||||||
* <p>
|
* <p>
|
||||||
* Type: STRING
|
* Type: STRING
|
||||||
*
|
*
|
||||||
* @see Documents#MIME_TYPE_DIR
|
* @see #MIME_TYPE_DIR
|
||||||
*/
|
*/
|
||||||
public static final String MIME_TYPE = "mime_type";
|
public static final String COLUMN_MIME_TYPE = "mime_type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name of a document, used as the primary title displayed to a
|
||||||
|
* user.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary of a document, which may be shown to a user. The summary may
|
||||||
|
* be {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_SUMMARY = "summary";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when a document was last modified, in milliseconds since
|
* Timestamp when a document was last modified, in milliseconds since
|
||||||
* January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
|
* January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
|
||||||
* providers can update this field using events from
|
* {@link DocumentsProvider} can update this field using events from
|
||||||
* {@link OnCloseListener} or other reliable
|
* {@link OnCloseListener} or other reliable
|
||||||
* {@link ParcelFileDescriptor} transports.
|
* {@link ParcelFileDescriptor} transports.
|
||||||
* <p>
|
* <p>
|
||||||
@ -283,71 +144,227 @@ public final class DocumentsContract {
|
|||||||
*
|
*
|
||||||
* @see System#currentTimeMillis()
|
* @see System#currentTimeMillis()
|
||||||
*/
|
*/
|
||||||
public static final String LAST_MODIFIED = "last_modified";
|
public static final String COLUMN_LAST_MODIFIED = "last_modified";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific icon resource for a document, or {@code null} to resolve
|
* Specific icon resource ID for a document, or {@code null} to use
|
||||||
* default using {@link #MIME_TYPE}.
|
* platform default icon based on {@link #COLUMN_MIME_TYPE}.
|
||||||
* <p>
|
* <p>
|
||||||
* Type: INTEGER (int)
|
* Type: INTEGER (int)
|
||||||
*/
|
*/
|
||||||
public static final String ICON = "icon";
|
public static final String COLUMN_ICON = "icon";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summary for a document, or {@code null} to omit.
|
* Flags that apply to a document.
|
||||||
* <p>
|
|
||||||
* Type: STRING
|
|
||||||
*/
|
|
||||||
public static final String SUMMARY = "summary";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flags that apply to a specific document.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Type: INTEGER (int)
|
* Type: INTEGER (int)
|
||||||
|
*
|
||||||
|
* @see #FLAG_SUPPORTS_WRITE
|
||||||
|
* @see #FLAG_SUPPORTS_DELETE
|
||||||
|
* @see #FLAG_SUPPORTS_THUMBNAIL
|
||||||
|
* @see #FLAG_DIR_PREFERS_GRID
|
||||||
|
* @see #FLAG_DIR_SUPPORTS_CREATE
|
||||||
|
* @see #FLAG_DIR_SUPPORTS_SEARCH
|
||||||
*/
|
*/
|
||||||
public static final String FLAGS = "flags";
|
public static final String COLUMN_FLAGS = "flags";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of a document, in bytes, or {@code null} if unknown.
|
||||||
|
* <p>
|
||||||
|
* Type: INTEGER (long)
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_SIZE = OpenableColumns.SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME type of a document which is a directory that may contain
|
||||||
|
* additional documents.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_MIME_TYPE
|
||||||
|
*/
|
||||||
|
public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a document can be represented as a thumbnail.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
|
||||||
|
* Point, CancellationSignal)
|
||||||
|
* @see DocumentsProvider#openDocumentThumbnail(String, Point,
|
||||||
|
* android.os.CancellationSignal)
|
||||||
|
*/
|
||||||
|
public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a document supports writing.
|
||||||
|
* <p>
|
||||||
|
* When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
|
||||||
|
* the calling application is granted both
|
||||||
|
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
|
||||||
|
* {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
|
||||||
|
* writability of a document may change over time, for example due to
|
||||||
|
* remote access changes. This flag indicates that a document client can
|
||||||
|
* expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
*/
|
||||||
|
public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a document is deletable.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see DocumentsContract#deleteDocument(ContentResolver, Uri)
|
||||||
|
* @see DocumentsProvider#deleteDocument(String)
|
||||||
|
*/
|
||||||
|
public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a document is a directory that supports creation
|
||||||
|
* of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
|
||||||
|
* {@link #MIME_TYPE_DIR}.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see DocumentsContract#createDocument(ContentResolver, Uri, String,
|
||||||
|
* String)
|
||||||
|
* @see DocumentsProvider#createDocument(String, String, String)
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a directory supports search. Only valid when
|
||||||
|
* {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see DocumentsProvider#querySearchDocuments(String, String,
|
||||||
|
* String[])
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating that a directory prefers its contents be shown in a
|
||||||
|
* larger format grid. Usually suitable when a directory contains mostly
|
||||||
|
* pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
|
||||||
|
* {@link #MIME_TYPE_DIR}.
|
||||||
|
*
|
||||||
|
* @see #COLUMN_FLAGS
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata about a specific root of documents.
|
* Constants related to a root of documents, including {@link Cursor}
|
||||||
|
* columns names and flags.
|
||||||
|
* <p>
|
||||||
|
* All columns are <em>read-only</em> to client applications.
|
||||||
*/
|
*/
|
||||||
public final static class DocumentRoot implements Parcelable {
|
public final static class Root {
|
||||||
|
private Root() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root that represents a storage service, such as a cloud-based
|
* Unique ID of a root. This ID is both provided by and interpreted by a
|
||||||
|
* {@link DocumentsProvider}, and should be treated as an opaque value
|
||||||
|
* by client applications.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_ROOT_ID = "root_id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of a root, used for clustering when presenting multiple roots to
|
||||||
|
* a user.
|
||||||
|
* <p>
|
||||||
|
* Type: INTEGER (int)
|
||||||
|
*
|
||||||
|
* @see #ROOT_TYPE_SERVICE
|
||||||
|
* @see #ROOT_TYPE_SHORTCUT
|
||||||
|
* @see #ROOT_TYPE_DEVICE
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_ROOT_TYPE = "root_type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags that apply to a root.
|
||||||
|
* <p>
|
||||||
|
* Type: INTEGER (int)
|
||||||
|
*
|
||||||
|
* @see #FLAG_LOCAL_ONLY
|
||||||
|
* @see #FLAG_SUPPORTS_CREATE
|
||||||
|
* @see #FLAG_ADVANCED
|
||||||
|
* @see #FLAG_PROVIDES_AUDIO
|
||||||
|
* @see #FLAG_PROVIDES_IMAGES
|
||||||
|
* @see #FLAG_PROVIDES_VIDEO
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_FLAGS = "flags";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon resource ID for a root.
|
||||||
|
* <p>
|
||||||
|
* Type: INTEGER (int)
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_ICON = "icon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title for a root, which will be shown to a user.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_TITLE = "title";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summary for this root, which may be shown to a user. The summary may
|
||||||
|
* be {@code null}.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_SUMMARY = "summary";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document which is a directory that represents the top directory of
|
||||||
|
* this root.
|
||||||
|
* <p>
|
||||||
|
* Type: STRING
|
||||||
|
*
|
||||||
|
* @see Document#COLUMN_DOCUMENT_ID
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_DOCUMENT_ID = "document_id";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes available in this root, or {@code null} if unknown or
|
||||||
|
* unbounded.
|
||||||
|
* <p>
|
||||||
|
* Type: INTEGER (long)
|
||||||
|
*/
|
||||||
|
public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of root that represents a storage service, such as a cloud-based
|
||||||
* service.
|
* service.
|
||||||
*
|
*
|
||||||
* @see #rootType
|
* @see #COLUMN_ROOT_TYPE
|
||||||
*/
|
*/
|
||||||
public static final int ROOT_TYPE_SERVICE = 1;
|
public static final int ROOT_TYPE_SERVICE = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root that represents a shortcut to content that may be available
|
* Type of root that represents a shortcut to content that may be
|
||||||
* elsewhere through another storage root.
|
* available elsewhere through another storage root.
|
||||||
*
|
*
|
||||||
* @see #rootType
|
* @see #COLUMN_ROOT_TYPE
|
||||||
*/
|
*/
|
||||||
public static final int ROOT_TYPE_SHORTCUT = 2;
|
public static final int ROOT_TYPE_SHORTCUT = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root that represents a physical storage device.
|
* Type of root that represents a physical storage device.
|
||||||
*
|
*
|
||||||
* @see #rootType
|
* @see #COLUMN_ROOT_TYPE
|
||||||
*/
|
*/
|
||||||
public static final int ROOT_TYPE_DEVICE = 3;
|
public static final int ROOT_TYPE_DEVICE = 3;
|
||||||
|
|
||||||
/**
|
|
||||||
* Root that represents a physical storage device that should only be
|
|
||||||
* displayed to advanced users.
|
|
||||||
*
|
|
||||||
* @see #rootType
|
|
||||||
*/
|
|
||||||
public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating that at least one directory under this root supports
|
* Flag indicating that at least one directory under this root supports
|
||||||
* creating content.
|
* creating content. Roots with this flag will be shown when an
|
||||||
|
* application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||||
*
|
*
|
||||||
* @see #flags
|
* @see #COLUMN_FLAGS
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_SUPPORTS_CREATE = 1;
|
public static final int FLAG_SUPPORTS_CREATE = 1;
|
||||||
|
|
||||||
@ -355,138 +372,201 @@ public final class DocumentsContract {
|
|||||||
* Flag indicating that this root offers content that is strictly local
|
* Flag indicating that this root offers content that is strictly local
|
||||||
* on the device. That is, no network requests are made for the content.
|
* on the device. That is, no network requests are made for the content.
|
||||||
*
|
*
|
||||||
* @see #flags
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see Intent#EXTRA_LOCAL_ONLY
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_LOCAL_ONLY = 1 << 1;
|
public static final int FLAG_LOCAL_ONLY = 1 << 1;
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public String authority;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root type, use for clustering.
|
* Flag indicating that this root should only be visible to advanced
|
||||||
|
* users.
|
||||||
*
|
*
|
||||||
* @see #ROOT_TYPE_SERVICE
|
* @see #COLUMN_FLAGS
|
||||||
* @see #ROOT_TYPE_DEVICE
|
|
||||||
*/
|
*/
|
||||||
public int rootType;
|
public static final int FLAG_ADVANCED = 1 << 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags for this root.
|
* Flag indicating that a root offers audio documents. When a user is
|
||||||
|
* selecting audio, roots not providing audio may be excluded.
|
||||||
*
|
*
|
||||||
* @see #FLAG_LOCAL_ONLY
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see Intent#EXTRA_MIME_TYPES
|
||||||
*/
|
*/
|
||||||
public int flags;
|
public static final int FLAG_PROVIDES_AUDIO = 1 << 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon resource ID for this root.
|
* Flag indicating that a root offers video documents. When a user is
|
||||||
*/
|
* selecting video, roots not providing video may be excluded.
|
||||||
public int icon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Title for this root.
|
|
||||||
*/
|
|
||||||
public String title;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Summary for this root. May be {@code null}.
|
|
||||||
*/
|
|
||||||
public String summary;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Document which is a directory that represents the top of this root.
|
|
||||||
* Must not be {@code null}.
|
|
||||||
*
|
*
|
||||||
* @see DocumentColumns#DOC_ID
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see Intent#EXTRA_MIME_TYPES
|
||||||
*/
|
*/
|
||||||
public String docId;
|
public static final int FLAG_PROVIDES_VIDEO = 1 << 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Document which is a directory representing recently modified
|
* Flag indicating that a root offers image documents. When a user is
|
||||||
* documents under this root. This directory should return at most two
|
* selecting images, roots not providing images may be excluded.
|
||||||
* dozen documents modified within the last 90 days. May be {@code null}
|
|
||||||
* if this root doesn't support recents.
|
|
||||||
*
|
*
|
||||||
* @see DocumentColumns#DOC_ID
|
* @see #COLUMN_FLAGS
|
||||||
|
* @see Intent#EXTRA_MIME_TYPES
|
||||||
*/
|
*/
|
||||||
public String recentDocId;
|
public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of free bytes of available in this root, or -1 if unknown or
|
* Optional boolean flag included in a directory {@link Cursor#getExtras()}
|
||||||
* unbounded.
|
* indicating that a document provider is still loading data. For example, a
|
||||||
*/
|
* provider has returned some results, but is still waiting on an
|
||||||
public long availableBytes;
|
* outstanding network request. The provider must send a content changed
|
||||||
|
* notification when loading is finished.
|
||||||
|
*
|
||||||
|
* @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
|
||||||
|
* boolean)
|
||||||
|
*/
|
||||||
|
public static final String EXTRA_LOADING = "loading";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of MIME type filters describing the content offered by this root,
|
* Optional string included in a directory {@link Cursor#getExtras()}
|
||||||
* or {@code null} to indicate that all MIME types are supported. For
|
* providing an informational message that should be shown to a user. For
|
||||||
* example, a provider only supporting audio and video might set this to
|
* example, a provider may wish to indicate that not all documents are
|
||||||
* {@code ["audio/*", "video/*"]}.
|
* available.
|
||||||
*/
|
*/
|
||||||
public String[] mimeTypes;
|
public static final String EXTRA_INFO = "info";
|
||||||
|
|
||||||
public DocumentRoot() {
|
/**
|
||||||
|
* Optional string included in a directory {@link Cursor#getExtras()}
|
||||||
|
* providing an error message that should be shown to a user. For example, a
|
||||||
|
* provider may wish to indicate that a network error occurred. The user may
|
||||||
|
* choose to retry, resulting in a new query.
|
||||||
|
*/
|
||||||
|
public static final String EXTRA_ERROR = "error";
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
|
||||||
|
/** {@hide} */
|
||||||
|
public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
|
public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
|
||||||
|
|
||||||
|
private static final String PATH_ROOT = "root";
|
||||||
|
private static final String PATH_RECENT = "recent";
|
||||||
|
private static final String PATH_DOCUMENT = "document";
|
||||||
|
private static final String PATH_CHILDREN = "children";
|
||||||
|
private static final String PATH_SEARCH = "search";
|
||||||
|
|
||||||
|
private static final String PARAM_QUERY = "query";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Uri representing the roots of a document provider. When queried, a
|
||||||
|
* provider will return one or more rows with columns defined by
|
||||||
|
* {@link Root}.
|
||||||
|
*
|
||||||
|
* @see DocumentsProvider#queryRoots(String[])
|
||||||
|
*/
|
||||||
|
public static Uri buildRootsUri(String authority) {
|
||||||
|
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(authority).appendPath(PATH_ROOT).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Uri representing the recently modified documents of a specific
|
||||||
|
* root. When queried, a provider will return zero or more rows with columns
|
||||||
|
* defined by {@link Document}.
|
||||||
|
*
|
||||||
|
* @see DocumentsProvider#queryRecentDocuments(String, String[])
|
||||||
|
* @see #getRootId(Uri)
|
||||||
|
*/
|
||||||
|
public static Uri buildRecentDocumentsUri(String authority, String rootId) {
|
||||||
|
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
|
||||||
|
.appendPath(PATH_RECENT).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
|
||||||
|
* document provider. When queried, a provider will return a single row with
|
||||||
|
* columns defined by {@link Document}.
|
||||||
|
*
|
||||||
|
* @see DocumentsProvider#queryDocument(String, String[])
|
||||||
|
* @see #getDocumentId(Uri)
|
||||||
|
*/
|
||||||
|
public static Uri buildDocumentUri(String authority, String documentId) {
|
||||||
|
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Uri representing the children of the given directory in a document
|
||||||
|
* provider. When queried, a provider will return zero or more rows with
|
||||||
|
* columns defined by {@link Document}.
|
||||||
|
*
|
||||||
|
* @param parentDocumentId the document to return children for, which must
|
||||||
|
* be a directory with MIME type of
|
||||||
|
* {@link Document#MIME_TYPE_DIR}.
|
||||||
|
* @see DocumentsProvider#queryChildDocuments(String, String[], String)
|
||||||
|
* @see #getDocumentId(Uri)
|
||||||
|
*/
|
||||||
|
public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
|
||||||
|
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
|
||||||
|
.appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Uri representing a search for matching documents under a specific
|
||||||
|
* directory in a document provider. When queried, a provider will return
|
||||||
|
* zero or more rows with columns defined by {@link Document}.
|
||||||
|
*
|
||||||
|
* @param parentDocumentId the document to return children for, which must
|
||||||
|
* be both a directory with MIME type of
|
||||||
|
* {@link Document#MIME_TYPE_DIR} and have
|
||||||
|
* {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set.
|
||||||
|
* @see DocumentsProvider#querySearchDocuments(String, String, String[])
|
||||||
|
* @see #getDocumentId(Uri)
|
||||||
|
* @see #getSearchDocumentsQuery(Uri)
|
||||||
|
*/
|
||||||
|
public static Uri buildSearchDocumentsUri(
|
||||||
|
String authority, String parentDocumentId, String query) {
|
||||||
|
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
|
||||||
|
.appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH)
|
||||||
|
.appendQueryParameter(PARAM_QUERY, query).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
|
||||||
|
*/
|
||||||
|
public static String getRootId(Uri rootUri) {
|
||||||
|
final List<String> paths = rootUri.getPathSegments();
|
||||||
|
if (paths.size() < 2) {
|
||||||
|
throw new IllegalArgumentException("Not a root: " + rootUri);
|
||||||
}
|
}
|
||||||
|
if (!PATH_ROOT.equals(paths.get(0))) {
|
||||||
/** {@hide} */
|
throw new IllegalArgumentException("Not a root: " + rootUri);
|
||||||
public DocumentRoot(Parcel in) {
|
|
||||||
rootType = in.readInt();
|
|
||||||
flags = in.readInt();
|
|
||||||
icon = in.readInt();
|
|
||||||
title = in.readString();
|
|
||||||
summary = in.readString();
|
|
||||||
docId = in.readString();
|
|
||||||
recentDocId = in.readString();
|
|
||||||
availableBytes = in.readLong();
|
|
||||||
mimeTypes = in.readStringArray();
|
|
||||||
}
|
}
|
||||||
|
return paths.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@hide} */
|
/**
|
||||||
public Drawable loadIcon(Context context) {
|
* Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
|
||||||
if (icon != 0) {
|
*/
|
||||||
if (authority != null) {
|
public static String getDocumentId(Uri documentUri) {
|
||||||
final PackageManager pm = context.getPackageManager();
|
final List<String> paths = documentUri.getPathSegments();
|
||||||
final ProviderInfo info = pm.resolveContentProvider(authority, 0);
|
if (paths.size() < 2) {
|
||||||
if (info != null) {
|
throw new IllegalArgumentException("Not a document: " + documentUri);
|
||||||
return pm.getDrawable(info.packageName, icon, info.applicationInfo);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return context.getResources().getDrawable(icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
if (!PATH_DOCUMENT.equals(paths.get(0))) {
|
||||||
@Override
|
throw new IllegalArgumentException("Not a document: " + documentUri);
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
return paths.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
* Extract the search query from a Uri built by
|
||||||
Preconditions.checkNotNull(docId);
|
* {@link #buildSearchDocumentsUri(String, String, String)}.
|
||||||
|
*/
|
||||||
dest.writeInt(rootType);
|
public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
|
||||||
dest.writeInt(flags);
|
return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
|
||||||
dest.writeInt(icon);
|
|
||||||
dest.writeString(title);
|
|
||||||
dest.writeString(summary);
|
|
||||||
dest.writeString(docId);
|
|
||||||
dest.writeString(recentDocId);
|
|
||||||
dest.writeLong(availableBytes);
|
|
||||||
dest.writeStringArray(mimeTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() {
|
|
||||||
@Override
|
|
||||||
public DocumentRoot createFromParcel(Parcel in) {
|
|
||||||
return new DocumentRoot(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DocumentRoot[] newArray(int size) {
|
|
||||||
return new DocumentRoot[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -497,6 +577,7 @@ public final class DocumentsContract {
|
|||||||
* {@link Intent#ACTION_CREATE_DOCUMENT}.
|
* {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||||
*
|
*
|
||||||
* @see Context#grantUriPermission(String, Uri, int)
|
* @see Context#grantUriPermission(String, Uri, int)
|
||||||
|
* @see Context#revokeUriPermission(Uri, int)
|
||||||
* @see ContentResolver#getIncomingUriPermissionGrants(int, int)
|
* @see ContentResolver#getIncomingUriPermissionGrants(int, int)
|
||||||
*/
|
*/
|
||||||
public static Uri[] getOpenDocuments(Context context) {
|
public static Uri[] getOpenDocuments(Context context) {
|
||||||
@ -520,20 +601,28 @@ public final class DocumentsContract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return thumbnail representing the document at the given URI. Callers are
|
* Return thumbnail representing the document at the given Uri. Callers are
|
||||||
* responsible for their own in-memory caching. Given document must have
|
* responsible for their own in-memory caching.
|
||||||
* {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
|
|
||||||
*
|
*
|
||||||
|
* @param documentUri document to return thumbnail for, which must have
|
||||||
|
* {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
|
||||||
|
* @param size optimal thumbnail size desired. A provider may return a
|
||||||
|
* thumbnail of a different size, but never more than double the
|
||||||
|
* requested size.
|
||||||
|
* @param signal signal used to indicate that caller is no longer interested
|
||||||
|
* in the thumbnail.
|
||||||
* @return decoded thumbnail, or {@code null} if problem was encountered.
|
* @return decoded thumbnail, or {@code null} if problem was encountered.
|
||||||
* @hide
|
* @see DocumentsProvider#openDocumentThumbnail(String, Point,
|
||||||
|
* android.os.CancellationSignal)
|
||||||
*/
|
*/
|
||||||
public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
|
public static Bitmap getDocumentThumbnail(
|
||||||
|
ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
|
||||||
final Bundle openOpts = new Bundle();
|
final Bundle openOpts = new Bundle();
|
||||||
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
|
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
|
||||||
|
|
||||||
AssetFileDescriptor afd = null;
|
AssetFileDescriptor afd = null;
|
||||||
try {
|
try {
|
||||||
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
|
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
|
||||||
|
|
||||||
final FileDescriptor fd = afd.getFileDescriptor();
|
final FileDescriptor fd = afd.getFileDescriptor();
|
||||||
final long offset = afd.getStartOffset();
|
final long offset = afd.getStartOffset();
|
||||||
@ -583,79 +672,43 @@ public final class DocumentsContract {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@hide} */
|
|
||||||
public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) {
|
|
||||||
try {
|
|
||||||
final Bundle out = client.call(METHOD_GET_ROOTS, null, null);
|
|
||||||
final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS);
|
|
||||||
return roots;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "Failed to get roots", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new document under the given parent document with MIME type and
|
* Create a new document with given MIME type and display name.
|
||||||
* display name.
|
|
||||||
*
|
*
|
||||||
* @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE}
|
* @param parentDocumentUri directory with
|
||||||
|
* {@link Document#FLAG_DIR_SUPPORTS_CREATE}
|
||||||
* @param mimeType MIME type of new document
|
* @param mimeType MIME type of new document
|
||||||
* @param displayName name of new document
|
* @param displayName name of new document
|
||||||
* @return newly created document, or {@code null} if failed
|
* @return newly created document, or {@code null} if failed
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
public static String createDocument(
|
public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
|
||||||
ContentProviderClient client, String docId, String mimeType, String displayName) {
|
String mimeType, String displayName) {
|
||||||
final Bundle in = new Bundle();
|
final Bundle in = new Bundle();
|
||||||
in.putString(DocumentColumns.DOC_ID, docId);
|
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
|
||||||
in.putString(DocumentColumns.MIME_TYPE, mimeType);
|
in.putString(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
in.putString(DocumentColumns.DISPLAY_NAME, displayName);
|
in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
|
final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
|
||||||
return out.getString(DocumentColumns.DOC_ID);
|
return buildDocumentUri(
|
||||||
|
parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Failed to create document", e);
|
Log.w(TAG, "Failed to create document", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename the given document.
|
|
||||||
*
|
|
||||||
* @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME}
|
|
||||||
* @return document which may have changed due to rename, or {@code null} if
|
|
||||||
* rename failed.
|
|
||||||
* @hide
|
|
||||||
*/
|
|
||||||
public static String renameDocument(
|
|
||||||
ContentProviderClient client, String docId, String displayName) {
|
|
||||||
final Bundle in = new Bundle();
|
|
||||||
in.putString(DocumentColumns.DOC_ID, docId);
|
|
||||||
in.putString(DocumentColumns.DISPLAY_NAME, displayName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
|
|
||||||
return out.getString(DocumentColumns.DOC_ID);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "Failed to rename document", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the given document.
|
* Delete the given document.
|
||||||
*
|
*
|
||||||
* @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
|
* @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
|
||||||
* @hide
|
|
||||||
*/
|
*/
|
||||||
public static boolean deleteDocument(ContentProviderClient client, String docId) {
|
public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
|
||||||
final Bundle in = new Bundle();
|
final Bundle in = new Bundle();
|
||||||
in.putString(DocumentColumns.DOC_ID, docId);
|
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client.call(METHOD_DELETE_DOCUMENT, null, in);
|
final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Failed to delete document", e);
|
Log.w(TAG, "Failed to delete document", e);
|
||||||
|
@ -16,16 +16,12 @@
|
|||||||
|
|
||||||
package android.provider;
|
package android.provider;
|
||||||
|
|
||||||
import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED;
|
|
||||||
import static android.provider.DocumentsContract.EXTRA_AUTHORITY;
|
|
||||||
import static android.provider.DocumentsContract.EXTRA_ROOTS;
|
|
||||||
import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
|
import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
|
||||||
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
|
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
|
||||||
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
|
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
|
||||||
import static android.provider.DocumentsContract.METHOD_GET_ROOTS;
|
import static android.provider.DocumentsContract.getDocumentId;
|
||||||
import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
|
import static android.provider.DocumentsContract.getRootId;
|
||||||
import static android.provider.DocumentsContract.getDocId;
|
import static android.provider.DocumentsContract.getSearchDocumentsQuery;
|
||||||
import static android.provider.DocumentsContract.getSearchQuery;
|
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@ -41,15 +37,12 @@ import android.os.Bundle;
|
|||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.ParcelFileDescriptor.OnCloseListener;
|
import android.os.ParcelFileDescriptor.OnCloseListener;
|
||||||
import android.provider.DocumentsContract.DocumentColumns;
|
import android.provider.DocumentsContract.Document;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
|
||||||
import android.provider.DocumentsContract.Documents;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for a document provider. A document provider should extend this
|
* Base class for a document provider. A document provider should extend this
|
||||||
@ -58,13 +51,13 @@ import java.util.List;
|
|||||||
* Each document provider expresses one or more "roots" which each serve as the
|
* Each document 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
|
* 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
|
* physical storage device. Under each root, documents are referenced by
|
||||||
* {@link DocumentColumns#DOC_ID}, which must not change once returned.
|
* {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
|
||||||
* <p>
|
* <p>
|
||||||
* Documents can be either an openable file (with a specific MIME type), or a
|
* Documents can be either an openable file (with a specific MIME type), or a
|
||||||
* directory containing additional documents (with the
|
* directory containing additional documents (with the
|
||||||
* {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
|
* {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
|
||||||
* capabilities, as described by {@link DocumentColumns#FLAGS}. The same
|
* capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
|
||||||
* {@link DocumentColumns#DOC_ID} can be included in multiple directories.
|
* {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
|
||||||
* <p>
|
* <p>
|
||||||
* Document providers must be protected with the
|
* Document providers must be protected with the
|
||||||
* {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
|
* {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
|
||||||
@ -78,22 +71,29 @@ import java.util.List;
|
|||||||
public abstract class DocumentsProvider extends ContentProvider {
|
public abstract class DocumentsProvider extends ContentProvider {
|
||||||
private static final String TAG = "DocumentsProvider";
|
private static final String TAG = "DocumentsProvider";
|
||||||
|
|
||||||
private static final int MATCH_DOCUMENT = 1;
|
private static final int MATCH_ROOT = 1;
|
||||||
private static final int MATCH_CHILDREN = 2;
|
private static final int MATCH_RECENT = 2;
|
||||||
private static final int MATCH_SEARCH = 3;
|
private static final int MATCH_DOCUMENT = 3;
|
||||||
|
private static final int MATCH_CHILDREN = 4;
|
||||||
|
private static final int MATCH_SEARCH = 5;
|
||||||
|
|
||||||
private String mAuthority;
|
private String mAuthority;
|
||||||
|
|
||||||
private UriMatcher mMatcher;
|
private UriMatcher mMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void attachInfo(Context context, ProviderInfo info) {
|
public void attachInfo(Context context, ProviderInfo info) {
|
||||||
mAuthority = info.authority;
|
mAuthority = info.authority;
|
||||||
|
|
||||||
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||||
mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT);
|
mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
|
||||||
mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN);
|
mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
|
||||||
mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH);
|
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
|
||||||
|
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
|
||||||
|
mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
|
||||||
|
|
||||||
// Sanity check our setup
|
// Sanity check our setup
|
||||||
if (!info.exported) {
|
if (!info.exported) {
|
||||||
@ -111,83 +111,80 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of all document roots provided by this document provider.
|
* Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
|
||||||
* When this list changes, a provider must call
|
* A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
|
||||||
* {@link #notifyDocumentRootsChanged()}.
|
* represent the document, which must not change once returned.
|
||||||
*/
|
|
||||||
public abstract List<DocumentRoot> getDocumentRoots();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and return a new document. A provider must allocate a new
|
|
||||||
* {@link DocumentColumns#DOC_ID} to represent the document, which must not
|
|
||||||
* change once returned.
|
|
||||||
*
|
*
|
||||||
* @param docId the parent directory to create the new document under.
|
* @param documentId the parent directory to create the new document under.
|
||||||
* @param mimeType the MIME type associated with the new document.
|
* @param mimeType the MIME type associated with the new document.
|
||||||
* @param displayName the display name of the new document.
|
* @param displayName the display name of the new document.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public String createDocument(String docId, String mimeType, String displayName)
|
public String createDocument(String documentId, String mimeType, String displayName)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
throw new UnsupportedOperationException("Create not supported");
|
throw new UnsupportedOperationException("Create not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename the given document.
|
* Delete the given document. Upon returning, any Uri permission grants for
|
||||||
|
* the given document will be revoked. If additional documents were deleted
|
||||||
|
* as a side effect of this call, such as documents inside a directory, the
|
||||||
|
* implementor is responsible for revoking those permissions.
|
||||||
*
|
*
|
||||||
* @param docId the document to rename.
|
* @param documentId the document to delete.
|
||||||
* @param displayName the new display name.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public void renameDocument(String docId, String displayName) throws FileNotFoundException {
|
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
throw new UnsupportedOperationException("Rename not supported");
|
throw new UnsupportedOperationException("Delete not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
|
||||||
* Delete the given document.
|
|
||||||
*
|
|
||||||
* @param docId the document to delete.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public void deleteDocument(String docId) throws FileNotFoundException {
|
public Cursor queryRecentDocuments(String rootId, String[] projection)
|
||||||
throw new UnsupportedOperationException("Delete not supported");
|
throws FileNotFoundException {
|
||||||
|
throw new UnsupportedOperationException("Recent not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return metadata for the given document. A provider should avoid making
|
* Return metadata for the given document. A provider should avoid making
|
||||||
* network requests to keep this request fast.
|
* network requests to keep this request fast.
|
||||||
*
|
*
|
||||||
* @param docId the document to return.
|
* @param documentId the document to return.
|
||||||
*/
|
*/
|
||||||
public abstract Cursor queryDocument(String docId) throws FileNotFoundException;
|
public abstract Cursor queryDocument(String documentId, String[] projection)
|
||||||
|
throws FileNotFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the children of the given document which is a directory.
|
* Return the children of the given document which is a directory.
|
||||||
*
|
*
|
||||||
* @param docId the directory to return children for.
|
* @param parentDocumentId the directory to return children for.
|
||||||
*/
|
*/
|
||||||
public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException;
|
public abstract Cursor queryChildDocuments(
|
||||||
|
String parentDocumentId, String[] projection, String sortOrder)
|
||||||
|
throws FileNotFoundException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return documents that that match the given query, starting the search at
|
* Return documents that that match the given query, starting the search at
|
||||||
* the given directory.
|
* the given directory.
|
||||||
*
|
*
|
||||||
* @param docId the directory to start search at.
|
* @param parentDocumentId the directory to start search at.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public Cursor querySearch(String docId, String query) throws FileNotFoundException {
|
public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
|
||||||
|
throws FileNotFoundException {
|
||||||
throw new UnsupportedOperationException("Search not supported");
|
throw new UnsupportedOperationException("Search not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return MIME type for the given document. Must match the value of
|
* Return MIME type for the given document. Must match the value of
|
||||||
* {@link DocumentColumns#MIME_TYPE} for this document.
|
* {@link Document#COLUMN_MIME_TYPE} for this document.
|
||||||
*/
|
*/
|
||||||
public String getType(String docId) throws FileNotFoundException {
|
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||||
final Cursor cursor = queryDocument(docId);
|
final Cursor cursor = queryDocument(documentId, null);
|
||||||
try {
|
try {
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE));
|
return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -233,7 +230,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
* @param sizeHint hint of the optimal thumbnail dimensions.
|
* @param sizeHint hint of the optimal thumbnail dimensions.
|
||||||
* @param signal used by the caller to signal if the request should be
|
* @param signal used by the caller to signal if the request should be
|
||||||
* cancelled.
|
* cancelled.
|
||||||
* @see Documents#FLAG_SUPPORTS_THUMBNAIL
|
* @see Document#FLAG_SUPPORTS_THUMBNAIL
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public AssetFileDescriptor openDocumentThumbnail(
|
public AssetFileDescriptor openDocumentThumbnail(
|
||||||
@ -241,17 +238,31 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
throw new UnsupportedOperationException("Thumbnails not supported");
|
throw new UnsupportedOperationException("Thumbnails not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class. Cannot be overriden.
|
||||||
|
*
|
||||||
|
* @see #queryRoots(String[])
|
||||||
|
* @see #queryRecentDocuments(String, String[])
|
||||||
|
* @see #queryDocument(String, String[])
|
||||||
|
* @see #queryChildDocuments(String, String[], String)
|
||||||
|
* @see #querySearchDocuments(String, String, String[])
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
public final Cursor query(Uri uri, String[] projection, String selection,
|
||||||
String sortOrder) {
|
String[] selectionArgs, String sortOrder) {
|
||||||
try {
|
try {
|
||||||
switch (mMatcher.match(uri)) {
|
switch (mMatcher.match(uri)) {
|
||||||
|
case MATCH_ROOT:
|
||||||
|
return queryRoots(projection);
|
||||||
|
case MATCH_RECENT:
|
||||||
|
return queryRecentDocuments(getRootId(uri), projection);
|
||||||
case MATCH_DOCUMENT:
|
case MATCH_DOCUMENT:
|
||||||
return queryDocument(getDocId(uri));
|
return queryDocument(getDocumentId(uri), projection);
|
||||||
case MATCH_CHILDREN:
|
case MATCH_CHILDREN:
|
||||||
return queryDocumentChildren(getDocId(uri));
|
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
|
||||||
case MATCH_SEARCH:
|
case MATCH_SEARCH:
|
||||||
return querySearch(getDocId(uri), getSearchQuery(uri));
|
return querySearchDocuments(
|
||||||
|
getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||||
}
|
}
|
||||||
@ -261,12 +272,17 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class. Cannot be overriden.
|
||||||
|
*
|
||||||
|
* @see #getDocumentType(String)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final String getType(Uri uri) {
|
public final String getType(Uri uri) {
|
||||||
try {
|
try {
|
||||||
switch (mMatcher.match(uri)) {
|
switch (mMatcher.match(uri)) {
|
||||||
case MATCH_DOCUMENT:
|
case MATCH_DOCUMENT:
|
||||||
return getType(getDocId(uri));
|
return getDocumentType(getDocumentId(uri));
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -276,22 +292,39 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class. Throws by default, and
|
||||||
|
* cannot be overriden.
|
||||||
|
*
|
||||||
|
* @see #createDocument(String, String, String)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final Uri insert(Uri uri, ContentValues values) {
|
public final Uri insert(Uri uri, ContentValues values) {
|
||||||
throw new UnsupportedOperationException("Insert not supported");
|
throw new UnsupportedOperationException("Insert not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class. Throws by default, and
|
||||||
|
* cannot be overriden.
|
||||||
|
*
|
||||||
|
* @see #deleteDocument(String)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final int delete(Uri uri, String selection, String[] selectionArgs) {
|
public final int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||||
throw new UnsupportedOperationException("Delete not supported");
|
throw new UnsupportedOperationException("Delete not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class. Throws by default, and
|
||||||
|
* cannot be overriden.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final int update(
|
public final int update(
|
||||||
Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
throw new UnsupportedOperationException("Update not supported");
|
throw new UnsupportedOperationException("Update not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@hide} */
|
||||||
@Override
|
@Override
|
||||||
public final Bundle callFromPackage(
|
public final Bundle callFromPackage(
|
||||||
String callingPackage, String method, String arg, Bundle extras) {
|
String callingPackage, String method, String arg, Bundle extras) {
|
||||||
@ -300,33 +333,25 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
return super.callFromPackage(callingPackage, method, arg, extras);
|
return super.callFromPackage(callingPackage, method, arg, extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Platform operations require the caller explicitly hold manage
|
// Require that caller can manage given document
|
||||||
// permission; Uri permissions don't extend management operations.
|
final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
|
||||||
getContext().enforceCallingOrSelfPermission(
|
final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
|
||||||
android.Manifest.permission.MANAGE_DOCUMENTS, "Document management");
|
getContext().enforceCallingOrSelfUriPermission(
|
||||||
|
documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
|
||||||
|
|
||||||
final Bundle out = new Bundle();
|
final Bundle out = new Bundle();
|
||||||
try {
|
try {
|
||||||
if (METHOD_GET_ROOTS.equals(method)) {
|
if (METHOD_CREATE_DOCUMENT.equals(method)) {
|
||||||
final List<DocumentRoot> roots = getDocumentRoots();
|
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
|
||||||
out.putParcelableList(EXTRA_ROOTS, roots);
|
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
|
||||||
|
|
||||||
} else if (METHOD_CREATE_DOCUMENT.equals(method)) {
|
// TODO: issue Uri grant towards calling package
|
||||||
final String docId = extras.getString(DocumentColumns.DOC_ID);
|
// TODO: enforce that package belongs to caller
|
||||||
final String mimeType = extras.getString(DocumentColumns.MIME_TYPE);
|
final String newDocumentId = createDocument(documentId, mimeType, displayName);
|
||||||
final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
|
out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
|
||||||
|
|
||||||
// TODO: issue Uri grant towards caller
|
|
||||||
final String newDocId = createDocument(docId, mimeType, displayName);
|
|
||||||
out.putString(DocumentColumns.DOC_ID, newDocId);
|
|
||||||
|
|
||||||
} else if (METHOD_RENAME_DOCUMENT.equals(method)) {
|
|
||||||
final String docId = extras.getString(DocumentColumns.DOC_ID);
|
|
||||||
final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
|
|
||||||
renameDocument(docId, displayName);
|
|
||||||
|
|
||||||
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
|
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
|
||||||
final String docId = extras.getString(DocumentColumns.DOC_ID);
|
final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID);
|
||||||
deleteDocument(docId);
|
deleteDocument(docId);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -338,47 +363,57 @@ public abstract class DocumentsProvider extends ContentProvider {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class.
|
||||||
|
*
|
||||||
|
* @see #openDocument(String, String, CancellationSignal)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
return openDocument(getDocId(uri), mode, null);
|
return openDocument(getDocumentId(uri), mode, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class.
|
||||||
|
*
|
||||||
|
* @see #openDocument(String, String, CancellationSignal)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
|
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
return openDocument(getDocId(uri), mode, signal);
|
return openDocument(getDocumentId(uri), mode, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class.
|
||||||
|
*
|
||||||
|
* @see #openDocumentThumbnail(String, Point, CancellationSignal)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
|
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||||
return openDocumentThumbnail(getDocId(uri), sizeHint, null);
|
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
|
||||||
} else {
|
} else {
|
||||||
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
|
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation is provided by the parent class.
|
||||||
|
*
|
||||||
|
* @see #openDocumentThumbnail(String, Point, CancellationSignal)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final AssetFileDescriptor openTypedAssetFile(
|
public final AssetFileDescriptor openTypedAssetFile(
|
||||||
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
|
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||||
return openDocumentThumbnail(getDocId(uri), sizeHint, signal);
|
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
|
||||||
} else {
|
} else {
|
||||||
return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
|
return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Notify system that {@link #getDocumentRoots()} has changed, usually due to an
|
|
||||||
* account or device change.
|
|
||||||
*/
|
|
||||||
public void notifyDocumentRootsChanged() {
|
|
||||||
final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED);
|
|
||||||
intent.putExtra(EXTRA_AUTHORITY, mAuthority);
|
|
||||||
getContext().sendBroadcast(intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import android.app.AlertDialog;
|
|||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.DialogFragment;
|
import android.app.DialogFragment;
|
||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.content.ContentProviderClient;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@ -28,13 +27,13 @@ import android.content.DialogInterface.OnClickListener;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.Documents;
|
import android.provider.DocumentsContract.Document;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog to create a new directory.
|
* Dialog to create a new directory.
|
||||||
@ -67,24 +66,17 @@ public class CreateDirectoryFragment extends DialogFragment {
|
|||||||
final String displayName = text1.getText().toString();
|
final String displayName = text1.getText().toString();
|
||||||
|
|
||||||
final DocumentsActivity activity = (DocumentsActivity) getActivity();
|
final DocumentsActivity activity = (DocumentsActivity) getActivity();
|
||||||
final Document cwd = activity.getCurrentDirectory();
|
final DocumentInfo cwd = activity.getCurrentDirectory();
|
||||||
|
|
||||||
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
|
|
||||||
cwd.uri.getAuthority());
|
|
||||||
try {
|
try {
|
||||||
final String docId = DocumentsContract.createDocument(client,
|
final Uri childUri = DocumentsContract.createDocument(
|
||||||
DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
|
resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
|
||||||
displayName);
|
|
||||||
|
|
||||||
// Navigate into newly created child
|
// Navigate into newly created child
|
||||||
final Uri childUri = DocumentsContract.buildDocumentUri(
|
final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
|
||||||
cwd.uri.getAuthority(), docId);
|
|
||||||
final Document childDoc = Document.fromUri(resolver, childUri);
|
|
||||||
activity.onDocumentPicked(childDoc);
|
activity.onDocumentPicked(childDoc);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
|
||||||
} finally {
|
|
||||||
ContentProviderClient.closeQuietly(client);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.android.documentsui.DocumentsActivity.DisplayState;
|
import com.android.documentsui.DocumentsActivity.DisplayState;
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
import com.android.internal.util.Predicate;
|
import com.android.internal.util.Predicate;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
|
|
||||||
private AbsListView mCurrentView;
|
private AbsListView mCurrentView;
|
||||||
|
|
||||||
private Predicate<Document> mFilter;
|
private Predicate<DocumentInfo> mFilter;
|
||||||
|
|
||||||
public static final int TYPE_NORMAL = 1;
|
public static final int TYPE_NORMAL = 1;
|
||||||
public static final int TYPE_SEARCH = 2;
|
public static final int TYPE_SEARCH = 2;
|
||||||
@ -106,8 +106,8 @@ public class DirectoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void showSearch(FragmentManager fm, Uri uri, String query) {
|
public static void showSearch(FragmentManager fm, Uri uri, String query) {
|
||||||
final Uri searchUri = DocumentsContract.buildSearchUri(
|
final Uri searchUri = DocumentsContract.buildSearchDocumentsUri(
|
||||||
uri.getAuthority(), DocumentsContract.getDocId(uri), query);
|
uri.getAuthority(), DocumentsContract.getDocumentId(uri), query);
|
||||||
show(fm, TYPE_SEARCH, searchUri);
|
show(fm, TYPE_SEARCH, searchUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,21 +163,21 @@ public class DirectoryFragment extends Fragment {
|
|||||||
|
|
||||||
Uri contentsUri;
|
Uri contentsUri;
|
||||||
if (mType == TYPE_NORMAL) {
|
if (mType == TYPE_NORMAL) {
|
||||||
contentsUri = DocumentsContract.buildChildrenUri(
|
contentsUri = DocumentsContract.buildChildDocumentsUri(
|
||||||
uri.getAuthority(), DocumentsContract.getDocId(uri));
|
uri.getAuthority(), DocumentsContract.getDocumentId(uri));
|
||||||
} else if (mType == TYPE_RECENT_OPEN) {
|
} else if (mType == TYPE_RECENT_OPEN) {
|
||||||
contentsUri = RecentsProvider.buildRecentOpen();
|
contentsUri = RecentsProvider.buildRecentOpen();
|
||||||
} else {
|
} else {
|
||||||
contentsUri = uri;
|
contentsUri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Comparator<Document> sortOrder;
|
final Comparator<DocumentInfo> sortOrder;
|
||||||
if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
|
if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) {
|
||||||
sortOrder = new Document.LastModifiedComparator();
|
sortOrder = new DocumentInfo.LastModifiedComparator();
|
||||||
} else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
|
} else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) {
|
||||||
sortOrder = new Document.DisplayNameComparator();
|
sortOrder = new DocumentInfo.DisplayNameComparator();
|
||||||
} else if (state.sortOrder == SORT_ORDER_SIZE) {
|
} else if (state.sortOrder == SORT_ORDER_SIZE) {
|
||||||
sortOrder = new Document.SizeComparator();
|
sortOrder = new DocumentInfo.SizeComparator();
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
|
throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
|
||||||
}
|
}
|
||||||
@ -258,7 +258,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
private OnItemClickListener mItemListener = new OnItemClickListener() {
|
private OnItemClickListener mItemListener = new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
final Document doc = mAdapter.getItem(position);
|
final DocumentInfo doc = mAdapter.getItem(position);
|
||||||
if (mFilter.apply(doc)) {
|
if (mFilter.apply(doc)) {
|
||||||
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
|
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
|
||||||
}
|
}
|
||||||
@ -291,11 +291,11 @@ public class DirectoryFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
|
final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
|
||||||
final ArrayList<Document> docs = Lists.newArrayList();
|
final ArrayList<DocumentInfo> docs = Lists.newArrayList();
|
||||||
final int size = checked.size();
|
final int size = checked.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
if (checked.valueAt(i)) {
|
if (checked.valueAt(i)) {
|
||||||
final Document doc = mAdapter.getItem(checked.keyAt(i));
|
final DocumentInfo doc = mAdapter.getItem(checked.keyAt(i));
|
||||||
docs.add(doc);
|
docs.add(doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,7 +328,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
ActionMode mode, int position, long id, boolean checked) {
|
ActionMode mode, int position, long id, boolean checked) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// Directories cannot be checked
|
// Directories cannot be checked
|
||||||
final Document doc = mAdapter.getItem(position);
|
final DocumentInfo doc = mAdapter.getItem(position);
|
||||||
if (doc.isDirectory()) {
|
if (doc.isDirectory()) {
|
||||||
mCurrentView.setItemChecked(position, false);
|
mCurrentView.setItemChecked(position, false);
|
||||||
}
|
}
|
||||||
@ -339,9 +339,9 @@ public class DirectoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void onShareDocuments(List<Document> docs) {
|
private void onShareDocuments(List<DocumentInfo> docs) {
|
||||||
final ArrayList<Uri> uris = Lists.newArrayList();
|
final ArrayList<Uri> uris = Lists.newArrayList();
|
||||||
for (Document doc : docs) {
|
for (DocumentInfo doc : docs) {
|
||||||
uris.add(doc.uri);
|
uris.add(doc.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,12 +363,12 @@ public class DirectoryFragment extends Fragment {
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDeleteDocuments(List<Document> docs) {
|
private void onDeleteDocuments(List<DocumentInfo> docs) {
|
||||||
final Context context = getActivity();
|
final Context context = getActivity();
|
||||||
final ContentResolver resolver = context.getContentResolver();
|
final ContentResolver resolver = context.getContentResolver();
|
||||||
|
|
||||||
boolean hadTrouble = false;
|
boolean hadTrouble = false;
|
||||||
for (Document doc : docs) {
|
for (DocumentInfo doc : docs) {
|
||||||
if (!doc.isDeleteSupported()) {
|
if (!doc.isDeleteSupported()) {
|
||||||
Log.w(TAG, "Skipping " + doc);
|
Log.w(TAG, "Skipping " + doc);
|
||||||
hadTrouble = true;
|
hadTrouble = true;
|
||||||
@ -396,12 +396,12 @@ public class DirectoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class DocumentsAdapter extends BaseAdapter {
|
private class DocumentsAdapter extends BaseAdapter {
|
||||||
private List<Document> mDocuments;
|
private List<DocumentInfo> mDocuments;
|
||||||
|
|
||||||
public DocumentsAdapter() {
|
public DocumentsAdapter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void swapDocuments(List<Document> documents) {
|
public void swapDocuments(List<DocumentInfo> documents) {
|
||||||
mDocuments = documents;
|
mDocuments = documents;
|
||||||
|
|
||||||
if (mDocuments != null && mDocuments.isEmpty()) {
|
if (mDocuments != null && mDocuments.isEmpty()) {
|
||||||
@ -433,7 +433,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Document doc = getItem(position);
|
final DocumentInfo doc = getItem(position);
|
||||||
|
|
||||||
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
|
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
|
||||||
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||||
@ -507,7 +507,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Document getItem(int position) {
|
public DocumentInfo getItem(int position) {
|
||||||
return mDocuments.get(position);
|
return mDocuments.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,8 +538,8 @@ public class DirectoryFragment extends Fragment {
|
|||||||
|
|
||||||
Bitmap result = null;
|
Bitmap result = null;
|
||||||
try {
|
try {
|
||||||
result = DocumentsContract.getThumbnail(
|
result = DocumentsContract.getDocumentThumbnail(
|
||||||
context.getContentResolver(), uri, mThumbSize);
|
context.getContentResolver(), uri, mThumbSize, null);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
|
final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
|
||||||
context, mThumbSize);
|
context, mThumbSize);
|
||||||
|
@ -28,7 +28,7 @@ import android.net.Uri;
|
|||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
import com.android.internal.util.Predicate;
|
import com.android.internal.util.Predicate;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ import java.util.List;
|
|||||||
|
|
||||||
class DirectoryResult implements AutoCloseable {
|
class DirectoryResult implements AutoCloseable {
|
||||||
Cursor cursor;
|
Cursor cursor;
|
||||||
List<Document> contents = Lists.newArrayList();
|
List<DocumentInfo> contents = Lists.newArrayList();
|
||||||
Exception e;
|
Exception e;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,11 +53,11 @@ class DirectoryResult implements AutoCloseable {
|
|||||||
public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
||||||
|
|
||||||
private final int mType;
|
private final int mType;
|
||||||
private Predicate<Document> mFilter;
|
private Predicate<DocumentInfo> mFilter;
|
||||||
private Comparator<Document> mSortOrder;
|
private Comparator<DocumentInfo> mSortOrder;
|
||||||
|
|
||||||
public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
|
public DirectoryLoader(Context context, Uri uri, int type, Predicate<DocumentInfo> filter,
|
||||||
Comparator<Document> sortOrder) {
|
Comparator<DocumentInfo> sortOrder) {
|
||||||
super(context, uri);
|
super(context, uri);
|
||||||
mType = type;
|
mType = type;
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
@ -84,15 +84,15 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
|||||||
result.cursor.registerContentObserver(mObserver);
|
result.cursor.registerContentObserver(mObserver);
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
Document doc = null;
|
DocumentInfo doc = null;
|
||||||
switch (mType) {
|
switch (mType) {
|
||||||
case TYPE_NORMAL:
|
case TYPE_NORMAL:
|
||||||
case TYPE_SEARCH:
|
case TYPE_SEARCH:
|
||||||
doc = Document.fromDirectoryCursor(uri, cursor);
|
doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
|
||||||
break;
|
break;
|
||||||
case TYPE_RECENT_OPEN:
|
case TYPE_RECENT_OPEN:
|
||||||
try {
|
try {
|
||||||
doc = Document.fromRecentOpenCursor(resolver, cursor);
|
doc = DocumentInfo.fromRecentOpenCursor(resolver, cursor);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Log.w(TAG, "Failed to find recent: " + e);
|
Log.w(TAG, "Failed to find recent: " + e);
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,10 @@ import static com.android.documentsui.DocumentsActivity.TAG;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles {@link DocumentRoot} changes which invalidate cached data.
|
* Handles changes which invalidate cached data.
|
||||||
*/
|
*/
|
||||||
public class DocumentChangedReceiver extends BroadcastReceiver {
|
public class DocumentChangedReceiver extends BroadcastReceiver {
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,7 +42,6 @@ import android.graphics.drawable.ColorDrawable;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
|
||||||
import android.support.v4.app.ActionBarDrawerToggle;
|
import android.support.v4.app.ActionBarDrawerToggle;
|
||||||
import android.support.v4.view.GravityCompat;
|
import android.support.v4.view.GravityCompat;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
@ -60,8 +59,9 @@ import android.widget.SearchView.OnQueryTextListener;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
import com.android.documentsui.model.DocumentStack;
|
import com.android.documentsui.model.DocumentStack;
|
||||||
|
import com.android.documentsui.model.RootInfo;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -160,7 +160,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||||
|
|
||||||
final Uri rootUri = intent.getData();
|
final Uri rootUri = intent.getData();
|
||||||
final DocumentRoot root = mRoots.findRoot(rootUri);
|
final RootInfo root = mRoots.findRoot(rootUri);
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
onRootPicked(root, true);
|
onRootPicked(root, true);
|
||||||
} else {
|
} else {
|
||||||
@ -252,7 +252,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
mDrawerToggle.setDrawerIndicatorEnabled(true);
|
mDrawerToggle.setDrawerIndicatorEnabled(true);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
final DocumentRoot root = getCurrentRoot();
|
final RootInfo root = getCurrentRoot();
|
||||||
actionBar.setIcon(root != null ? root.loadIcon(this) : null);
|
actionBar.setIcon(root != null ? root.loadIcon(this) : null);
|
||||||
|
|
||||||
if (mRoots.isRecentsRoot(root)) {
|
if (mRoots.isRecentsRoot(root)) {
|
||||||
@ -317,7 +317,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
super.onPrepareOptionsMenu(menu);
|
super.onPrepareOptionsMenu(menu);
|
||||||
|
|
||||||
final FragmentManager fm = getFragmentManager();
|
final FragmentManager fm = getFragmentManager();
|
||||||
final Document cwd = getCurrentDirectory();
|
final DocumentInfo cwd = getCurrentDirectory();
|
||||||
|
|
||||||
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
|
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
|
||||||
final MenuItem search = menu.findItem(R.id.menu_search);
|
final MenuItem search = menu.findItem(R.id.menu_search);
|
||||||
@ -473,7 +473,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public DocumentRoot getCurrentRoot() {
|
public RootInfo getCurrentRoot() {
|
||||||
if (mStack.size() > 0) {
|
if (mStack.size() > 0) {
|
||||||
return mStack.getRoot(mRoots);
|
return mStack.getRoot(mRoots);
|
||||||
} else {
|
} else {
|
||||||
@ -481,7 +481,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Document getCurrentDirectory() {
|
public DocumentInfo getCurrentDirectory() {
|
||||||
return mStack.peek();
|
return mStack.peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,7 +491,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
|
|
||||||
private void onCurrentDirectoryChanged() {
|
private void onCurrentDirectoryChanged() {
|
||||||
final FragmentManager fm = getFragmentManager();
|
final FragmentManager fm = getFragmentManager();
|
||||||
final Document cwd = getCurrentDirectory();
|
final DocumentInfo cwd = getCurrentDirectory();
|
||||||
|
|
||||||
if (cwd == null) {
|
if (cwd == null) {
|
||||||
// No directory means recents
|
// No directory means recents
|
||||||
@ -533,14 +533,14 @@ public class DocumentsActivity extends Activity {
|
|||||||
onCurrentDirectoryChanged();
|
onCurrentDirectoryChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onRootPicked(DocumentRoot root, boolean closeDrawer) {
|
public void onRootPicked(RootInfo root, boolean closeDrawer) {
|
||||||
// Clear entire backstack and start in new root
|
// Clear entire backstack and start in new root
|
||||||
mStack.clear();
|
mStack.clear();
|
||||||
|
|
||||||
if (!mRoots.isRecentsRoot(root)) {
|
if (!mRoots.isRecentsRoot(root)) {
|
||||||
try {
|
try {
|
||||||
final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId);
|
final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
|
||||||
onDocumentPicked(Document.fromUri(getContentResolver(), uri));
|
onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri));
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -561,7 +561,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDocumentPicked(Document doc) {
|
public void onDocumentPicked(DocumentInfo doc) {
|
||||||
final FragmentManager fm = getFragmentManager();
|
final FragmentManager fm = getFragmentManager();
|
||||||
if (doc.isDirectory()) {
|
if (doc.isDirectory()) {
|
||||||
// TODO: query display mode user preference for this dir
|
// TODO: query display mode user preference for this dir
|
||||||
@ -591,7 +591,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDocumentsPicked(List<Document> docs) {
|
public void onDocumentsPicked(List<DocumentInfo> docs) {
|
||||||
if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
|
if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) {
|
||||||
final int size = docs.size();
|
final int size = docs.size();
|
||||||
final Uri[] uris = new Uri[size];
|
final Uri[] uris = new Uri[size];
|
||||||
@ -602,21 +602,19 @@ public class DocumentsActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSaveRequested(Document replaceTarget) {
|
public void onSaveRequested(DocumentInfo replaceTarget) {
|
||||||
onFinished(replaceTarget.uri);
|
onFinished(replaceTarget.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSaveRequested(String mimeType, String displayName) {
|
public void onSaveRequested(String mimeType, String displayName) {
|
||||||
final Document cwd = getCurrentDirectory();
|
final DocumentInfo cwd = getCurrentDirectory();
|
||||||
final String authority = cwd.uri.getAuthority();
|
final String authority = cwd.uri.getAuthority();
|
||||||
|
|
||||||
final ContentProviderClient client = getContentResolver()
|
final ContentProviderClient client = getContentResolver()
|
||||||
.acquireUnstableContentProviderClient(authority);
|
.acquireUnstableContentProviderClient(authority);
|
||||||
try {
|
try {
|
||||||
final String docId = DocumentsContract.createDocument(client,
|
final Uri childUri = DocumentsContract.createDocument(
|
||||||
DocumentsContract.getDocId(cwd.uri), mimeType, displayName);
|
getContentResolver(), cwd.uri, mimeType, displayName);
|
||||||
|
|
||||||
final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId);
|
|
||||||
onFinished(childUri);
|
onFinished(childUri);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
|
||||||
@ -701,7 +699,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
|
|
||||||
private void dumpStack() {
|
private void dumpStack() {
|
||||||
Log.d(TAG, "Current stack:");
|
Log.d(TAG, "Current stack:");
|
||||||
for (Document doc : mStack) {
|
for (DocumentInfo doc : mStack) {
|
||||||
Log.d(TAG, "--> " + doc);
|
Log.d(TAG, "--> " + doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package com.android.documentsui;
|
package com.android.documentsui;
|
||||||
|
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
import com.android.internal.util.Predicate;
|
import com.android.internal.util.Predicate;
|
||||||
|
|
||||||
public class MimePredicate implements Predicate<Document> {
|
public class MimePredicate implements Predicate<DocumentInfo> {
|
||||||
private final String[] mFilters;
|
private final String[] mFilters;
|
||||||
|
|
||||||
public MimePredicate(String[] filters) {
|
public MimePredicate(String[] filters) {
|
||||||
@ -27,7 +27,7 @@ public class MimePredicate implements Predicate<Document> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Document doc) {
|
public boolean apply(DocumentInfo doc) {
|
||||||
if (doc.isDirectory()) {
|
if (doc.isDirectory()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import android.database.Cursor;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
|
||||||
import android.text.TextUtils.TruncateAt;
|
import android.text.TextUtils.TruncateAt;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -43,6 +42,7 @@ import android.widget.ListView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.documentsui.model.DocumentStack;
|
import com.android.documentsui.model.DocumentStack;
|
||||||
|
import com.android.documentsui.model.RootInfo;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
@ -181,7 +181,7 @@ public class RecentsCreateFragment extends Fragment {
|
|||||||
final View summaryList = convertView.findViewById(R.id.summary_list);
|
final View summaryList = convertView.findViewById(R.id.summary_list);
|
||||||
|
|
||||||
final DocumentStack stack = getItem(position);
|
final DocumentStack stack = getItem(position);
|
||||||
final DocumentRoot root = stack.getRoot(roots);
|
final RootInfo root = stack.getRoot(roots);
|
||||||
icon.setImageDrawable(root.loadIcon(context));
|
icon.setImageDrawable(root.loadIcon(context));
|
||||||
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
@ -25,17 +25,21 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ProviderInfo;
|
import android.content.pm.ProviderInfo;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
import android.provider.DocumentsContract.Document;
|
||||||
import android.provider.DocumentsContract.Documents;
|
import android.provider.DocumentsContract.Root;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.documentsui.model.RootInfo;
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.util.Objects;
|
import com.android.internal.util.Objects;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,9 +52,9 @@ public class RootsCache {
|
|||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
public List<DocumentRoot> mRoots = Lists.newArrayList();
|
public List<RootInfo> mRoots = Lists.newArrayList();
|
||||||
|
|
||||||
private DocumentRoot mRecentsRoot;
|
private RootInfo mRecentsRoot;
|
||||||
|
|
||||||
public RootsCache(Context context) {
|
public RootsCache(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@ -66,12 +70,10 @@ public class RootsCache {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Create special root for recents
|
// Create special root for recents
|
||||||
final DocumentRoot root = new DocumentRoot();
|
final RootInfo root = new RootInfo();
|
||||||
root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
|
root.rootType = Root.ROOT_TYPE_SHORTCUT;
|
||||||
root.docId = null;
|
|
||||||
root.icon = R.drawable.ic_dir;
|
root.icon = R.drawable.ic_dir;
|
||||||
root.title = mContext.getString(R.string.root_recent);
|
root.title = mContext.getString(R.string.root_recent);
|
||||||
root.summary = null;
|
|
||||||
root.availableBytes = -1;
|
root.availableBytes = -1;
|
||||||
|
|
||||||
mRoots.add(root);
|
mRoots.add(root);
|
||||||
@ -89,28 +91,32 @@ public class RootsCache {
|
|||||||
|
|
||||||
// TODO: remove deprecated customRoots flag
|
// TODO: remove deprecated customRoots flag
|
||||||
// TODO: populate roots on background thread, and cache results
|
// TODO: populate roots on background thread, and cache results
|
||||||
|
final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
|
||||||
final ContentProviderClient client = resolver
|
final ContentProviderClient client = resolver
|
||||||
.acquireUnstableContentProviderClient(info.authority);
|
.acquireUnstableContentProviderClient(info.authority);
|
||||||
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client);
|
cursor = client.query(rootsUri, null, null, null, null);
|
||||||
for (DocumentRoot root : roots) {
|
while (cursor.moveToNext()) {
|
||||||
root.authority = info.authority;
|
final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
|
||||||
|
mRoots.add(root);
|
||||||
}
|
}
|
||||||
mRoots.addAll(roots);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
|
Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
|
||||||
} finally {
|
} finally {
|
||||||
|
IoUtils.closeQuietly(cursor);
|
||||||
ContentProviderClient.closeQuietly(client);
|
ContentProviderClient.closeQuietly(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DocumentRoot findRoot(Uri uri) {
|
@Deprecated
|
||||||
|
public RootInfo findRoot(Uri uri) {
|
||||||
final String authority = uri.getAuthority();
|
final String authority = uri.getAuthority();
|
||||||
final String docId = DocumentsContract.getDocId(uri);
|
final String docId = DocumentsContract.getDocumentId(uri);
|
||||||
for (DocumentRoot root : mRoots) {
|
for (RootInfo root : mRoots) {
|
||||||
if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) {
|
if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,23 +124,23 @@ public class RootsCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
@GuardedBy("ActivityThread")
|
||||||
public DocumentRoot getRecentsRoot() {
|
public RootInfo getRecentsRoot() {
|
||||||
return mRecentsRoot;
|
return mRecentsRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
@GuardedBy("ActivityThread")
|
||||||
public boolean isRecentsRoot(DocumentRoot root) {
|
public boolean isRecentsRoot(RootInfo root) {
|
||||||
return mRecentsRoot == root;
|
return mRecentsRoot == root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
@GuardedBy("ActivityThread")
|
||||||
public List<DocumentRoot> getRoots() {
|
public List<RootInfo> getRoots() {
|
||||||
return mRoots;
|
return mRoots;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
@GuardedBy("ActivityThread")
|
||||||
public static Drawable resolveDocumentIcon(Context context, String mimeType) {
|
public static Drawable resolveDocumentIcon(Context context, String mimeType) {
|
||||||
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
|
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||||
return context.getResources().getDrawable(R.drawable.ic_dir);
|
return context.getResources().getDrawable(R.drawable.ic_dir);
|
||||||
} else {
|
} else {
|
||||||
final PackageManager pm = context.getPackageManager();
|
final PackageManager pm = context.getPackageManager();
|
||||||
|
@ -26,7 +26,7 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
import android.provider.DocumentsContract.Root;
|
||||||
import android.text.format.Formatter;
|
import android.text.format.Formatter;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -40,7 +40,8 @@ import android.widget.ListView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.documentsui.SectionedListAdapter.SectionAdapter;
|
import com.android.documentsui.SectionedListAdapter.SectionAdapter;
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
|
import com.android.documentsui.model.RootInfo;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -101,8 +102,8 @@ public class RootsFragment extends Fragment {
|
|||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
|
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
|
||||||
final Object item = mAdapter.getItem(position);
|
final Object item = mAdapter.getItem(position);
|
||||||
if (item instanceof DocumentRoot) {
|
if (item instanceof RootInfo) {
|
||||||
activity.onRootPicked((DocumentRoot) item, true);
|
activity.onRootPicked((RootInfo) item, true);
|
||||||
} else if (item instanceof ResolveInfo) {
|
} else if (item instanceof ResolveInfo) {
|
||||||
activity.onAppPicked((ResolveInfo) item);
|
activity.onAppPicked((ResolveInfo) item);
|
||||||
} else {
|
} else {
|
||||||
@ -111,7 +112,7 @@ public class RootsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter {
|
private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
|
||||||
private int mHeaderId;
|
private int mHeaderId;
|
||||||
|
|
||||||
public RootsAdapter(Context context, int headerId) {
|
public RootsAdapter(Context context, int headerId) {
|
||||||
@ -131,15 +132,13 @@ public class RootsFragment extends Fragment {
|
|||||||
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||||
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
|
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
|
||||||
|
|
||||||
final DocumentRoot root = getItem(position);
|
final RootInfo root = getItem(position);
|
||||||
icon.setImageDrawable(root.loadIcon(context));
|
icon.setImageDrawable(root.loadIcon(context));
|
||||||
title.setText(root.title);
|
title.setText(root.title);
|
||||||
|
|
||||||
// Device summary is always available space
|
// Device summary is always available space
|
||||||
final String summaryText;
|
final String summaryText;
|
||||||
if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE
|
if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
|
||||||
|| root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED)
|
|
||||||
&& root.availableBytes >= 0) {
|
|
||||||
summaryText = context.getString(R.string.root_available_bytes,
|
summaryText = context.getString(R.string.root_available_bytes,
|
||||||
Formatter.formatFileSize(context, root.availableBytes));
|
Formatter.formatFileSize(context, root.availableBytes));
|
||||||
} else {
|
} else {
|
||||||
@ -215,28 +214,27 @@ public class RootsFragment extends Fragment {
|
|||||||
private final RootsAdapter mDevicesAdvanced;
|
private final RootsAdapter mDevicesAdvanced;
|
||||||
private final AppsAdapter mApps;
|
private final AppsAdapter mApps;
|
||||||
|
|
||||||
public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) {
|
public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) {
|
||||||
mServices = new RootsAdapter(context, R.string.root_type_service);
|
mServices = new RootsAdapter(context, R.string.root_type_service);
|
||||||
mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut);
|
mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut);
|
||||||
mDevices = new RootsAdapter(context, R.string.root_type_device);
|
mDevices = new RootsAdapter(context, R.string.root_type_device);
|
||||||
mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device);
|
mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device);
|
||||||
mApps = new AppsAdapter(context);
|
mApps = new AppsAdapter(context);
|
||||||
|
|
||||||
for (DocumentRoot root : roots) {
|
for (RootInfo root : roots) {
|
||||||
Log.d(TAG, "Found rootType=" + root.rootType);
|
Log.d(TAG, "Found rootType=" + root.rootType);
|
||||||
switch (root.rootType) {
|
switch (root.rootType) {
|
||||||
case DocumentRoot.ROOT_TYPE_SERVICE:
|
case Root.ROOT_TYPE_SERVICE:
|
||||||
mServices.add(root);
|
mServices.add(root);
|
||||||
break;
|
break;
|
||||||
case DocumentRoot.ROOT_TYPE_SHORTCUT:
|
case Root.ROOT_TYPE_SHORTCUT:
|
||||||
mShortcuts.add(root);
|
mShortcuts.add(root);
|
||||||
break;
|
break;
|
||||||
case DocumentRoot.ROOT_TYPE_DEVICE:
|
case Root.ROOT_TYPE_DEVICE:
|
||||||
mDevices.add(root);
|
|
||||||
mDevicesAdvanced.add(root);
|
|
||||||
break;
|
|
||||||
case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED:
|
|
||||||
mDevicesAdvanced.add(root);
|
mDevicesAdvanced.add(root);
|
||||||
|
if ((root.flags & Root.FLAG_ADVANCED) == 0) {
|
||||||
|
mDevices.add(root);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,14 +279,14 @@ public class RootsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RootComparator implements Comparator<DocumentRoot> {
|
public static class RootComparator implements Comparator<RootInfo> {
|
||||||
@Override
|
@Override
|
||||||
public int compare(DocumentRoot lhs, DocumentRoot rhs) {
|
public int compare(RootInfo lhs, RootInfo rhs) {
|
||||||
final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title);
|
final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
|
||||||
if (score != 0) {
|
if (score != 0) {
|
||||||
return score;
|
return score;
|
||||||
} else {
|
} else {
|
||||||
return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
|
return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import android.widget.Button;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.android.documentsui.model.Document;
|
import com.android.documentsui.model.DocumentInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display document title editor and save button.
|
* Display document title editor and save button.
|
||||||
@ -39,7 +39,7 @@ import com.android.documentsui.model.Document;
|
|||||||
public class SaveFragment extends Fragment {
|
public class SaveFragment extends Fragment {
|
||||||
public static final String TAG = "SaveFragment";
|
public static final String TAG = "SaveFragment";
|
||||||
|
|
||||||
private Document mReplaceTarget;
|
private DocumentInfo mReplaceTarget;
|
||||||
private EditText mDisplayName;
|
private EditText mDisplayName;
|
||||||
private Button mSave;
|
private Button mSave;
|
||||||
private boolean mIgnoreNextEdit;
|
private boolean mIgnoreNextEdit;
|
||||||
@ -128,7 +128,7 @@ public class SaveFragment extends Fragment {
|
|||||||
* without changing the filename. Can be set to {@code null} if user
|
* without changing the filename. Can be set to {@code null} if user
|
||||||
* navigates outside the target directory.
|
* navigates outside the target directory.
|
||||||
*/
|
*/
|
||||||
public void setReplaceTarget(Document replaceTarget) {
|
public void setReplaceTarget(DocumentInfo replaceTarget) {
|
||||||
mReplaceTarget = replaceTarget;
|
mReplaceTarget = replaceTarget;
|
||||||
|
|
||||||
if (mReplaceTarget != null) {
|
if (mReplaceTarget != null) {
|
||||||
|
@ -20,8 +20,7 @@ import android.content.ContentResolver;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.DocumentColumns;
|
import android.provider.DocumentsContract.Document;
|
||||||
import android.provider.DocumentsContract.Documents;
|
|
||||||
|
|
||||||
import com.android.documentsui.RecentsProvider;
|
import com.android.documentsui.RecentsProvider;
|
||||||
|
|
||||||
@ -31,9 +30,9 @@ import java.io.FileNotFoundException;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a single document.
|
* Representation of a {@link Document}.
|
||||||
*/
|
*/
|
||||||
public class Document {
|
public class DocumentInfo {
|
||||||
public final Uri uri;
|
public final Uri uri;
|
||||||
public final String mimeType;
|
public final String mimeType;
|
||||||
public final String displayName;
|
public final String displayName;
|
||||||
@ -42,7 +41,7 @@ public class Document {
|
|||||||
public final String summary;
|
public final String summary;
|
||||||
public final long size;
|
public final long size;
|
||||||
|
|
||||||
private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags,
|
private DocumentInfo(Uri uri, String mimeType, String displayName, long lastModified, int flags,
|
||||||
String summary, long size) {
|
String summary, long size) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
@ -53,23 +52,23 @@ public class Document {
|
|||||||
this.size = size;
|
this.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Document fromDirectoryCursor(Uri parent, Cursor cursor) {
|
public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) {
|
||||||
final String authority = parent.getAuthority();
|
final String authority = parent.getAuthority();
|
||||||
final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
|
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
|
||||||
|
|
||||||
final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
|
final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
|
||||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||||
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
|
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
|
||||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
|
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
|
||||||
|
|
||||||
return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
|
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
|
public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
|
final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
|
||||||
final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
|
final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
|
||||||
@ -80,14 +79,14 @@ public class Document {
|
|||||||
if (!cursor.moveToFirst()) {
|
if (!cursor.moveToFirst()) {
|
||||||
throw new FileNotFoundException("Missing details for " + uri);
|
throw new FileNotFoundException("Missing details for " + uri);
|
||||||
}
|
}
|
||||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS)
|
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
|
||||||
& Documents.FLAG_SUPPORTS_THUMBNAIL;
|
& Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
|
||||||
|
|
||||||
return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
|
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw asFileNotFoundException(t);
|
throw asFileNotFoundException(t);
|
||||||
} finally {
|
} finally {
|
||||||
@ -95,21 +94,21 @@ public class Document {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
|
public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = resolver.query(uri, null, null, null, null);
|
cursor = resolver.query(uri, null, null, null, null);
|
||||||
if (!cursor.moveToFirst()) {
|
if (!cursor.moveToFirst()) {
|
||||||
throw new FileNotFoundException("Missing details for " + uri);
|
throw new FileNotFoundException("Missing details for " + uri);
|
||||||
}
|
}
|
||||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||||
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
|
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
|
||||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
|
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
final long size = getCursorLong(cursor, Document.COLUMN_SIZE);
|
||||||
|
|
||||||
return new Document(uri, mimeType, displayName, lastModified, flags, summary, size);
|
return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw asFileNotFoundException(t);
|
throw asFileNotFoundException(t);
|
||||||
} finally {
|
} finally {
|
||||||
@ -123,30 +122,30 @@ public class Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCreateSupported() {
|
public boolean isCreateSupported() {
|
||||||
return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0;
|
return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSearchSupported() {
|
public boolean isSearchSupported() {
|
||||||
return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0;
|
return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isThumbnailSupported() {
|
public boolean isThumbnailSupported() {
|
||||||
return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDirectory() {
|
public boolean isDirectory() {
|
||||||
return Documents.MIME_TYPE_DIR.equals(mimeType);
|
return Document.MIME_TYPE_DIR.equals(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isGridPreferred() {
|
public boolean isGridPreferred() {
|
||||||
return (flags & Documents.FLAG_PREFERS_GRID) != 0;
|
return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDeleteSupported() {
|
public boolean isDeleteSupported() {
|
||||||
return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0;
|
return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getCursorString(Cursor cursor, String columnName) {
|
public static String getCursorString(Cursor cursor, String columnName) {
|
||||||
final int index = cursor.getColumnIndex(columnName);
|
final int index = cursor.getColumnIndex(columnName);
|
||||||
return (index != -1) ? cursor.getString(index) : null;
|
return (index != -1) ? cursor.getString(index) : null;
|
||||||
}
|
}
|
||||||
@ -154,7 +153,7 @@ public class Document {
|
|||||||
/**
|
/**
|
||||||
* Missing or null values are returned as -1.
|
* Missing or null values are returned as -1.
|
||||||
*/
|
*/
|
||||||
private static long getCursorLong(Cursor cursor, String columnName) {
|
public static long getCursorLong(Cursor cursor, String columnName) {
|
||||||
final int index = cursor.getColumnIndex(columnName);
|
final int index = cursor.getColumnIndex(columnName);
|
||||||
if (index == -1) return -1;
|
if (index == -1) return -1;
|
||||||
final String value = cursor.getString(index);
|
final String value = cursor.getString(index);
|
||||||
@ -166,14 +165,14 @@ public class Document {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getCursorInt(Cursor cursor, String columnName) {
|
public static int getCursorInt(Cursor cursor, String columnName) {
|
||||||
final int index = cursor.getColumnIndex(columnName);
|
final int index = cursor.getColumnIndex(columnName);
|
||||||
return (index != -1) ? cursor.getInt(index) : 0;
|
return (index != -1) ? cursor.getInt(index) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DisplayNameComparator implements Comparator<Document> {
|
public static class DisplayNameComparator implements Comparator<DocumentInfo> {
|
||||||
@Override
|
@Override
|
||||||
public int compare(Document lhs, Document rhs) {
|
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||||
final boolean leftDir = lhs.isDirectory();
|
final boolean leftDir = lhs.isDirectory();
|
||||||
final boolean rightDir = rhs.isDirectory();
|
final boolean rightDir = rhs.isDirectory();
|
||||||
|
|
||||||
@ -185,16 +184,16 @@ public class Document {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LastModifiedComparator implements Comparator<Document> {
|
public static class LastModifiedComparator implements Comparator<DocumentInfo> {
|
||||||
@Override
|
@Override
|
||||||
public int compare(Document lhs, Document rhs) {
|
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||||
return Long.compare(rhs.lastModified, lhs.lastModified);
|
return Long.compare(rhs.lastModified, lhs.lastModified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SizeComparator implements Comparator<Document> {
|
public static class SizeComparator implements Comparator<DocumentInfo> {
|
||||||
@Override
|
@Override
|
||||||
public int compare(Document lhs, Document rhs) {
|
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||||
return Long.compare(rhs.size, lhs.size);
|
return Long.compare(rhs.size, lhs.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,11 +17,10 @@
|
|||||||
package com.android.documentsui.model;
|
package com.android.documentsui.model;
|
||||||
|
|
||||||
import static com.android.documentsui.DocumentsActivity.TAG;
|
import static com.android.documentsui.DocumentsActivity.TAG;
|
||||||
import static com.android.documentsui.model.Document.asFileNotFoundException;
|
import static com.android.documentsui.model.DocumentInfo.asFileNotFoundException;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.documentsui.RootsCache;
|
import com.android.documentsui.RootsCache;
|
||||||
@ -33,10 +32,10 @@ import java.io.FileNotFoundException;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a stack of {@link Document}, usually the result of a
|
* Representation of a stack of {@link DocumentInfo}, usually the result of a
|
||||||
* user-driven traversal.
|
* user-driven traversal.
|
||||||
*/
|
*/
|
||||||
public class DocumentStack extends LinkedList<Document> {
|
public class DocumentStack extends LinkedList<DocumentInfo> {
|
||||||
|
|
||||||
public static String serialize(DocumentStack stack) {
|
public static String serialize(DocumentStack stack) {
|
||||||
final JSONArray json = new JSONArray();
|
final JSONArray json = new JSONArray();
|
||||||
@ -55,7 +54,7 @@ public class DocumentStack extends LinkedList<Document> {
|
|||||||
final JSONArray json = new JSONArray(raw);
|
final JSONArray json = new JSONArray(raw);
|
||||||
for (int i = 0; i < json.length(); i++) {
|
for (int i = 0; i < json.length(); i++) {
|
||||||
final Uri uri = Uri.parse(json.getString(i));
|
final Uri uri = Uri.parse(json.getString(i));
|
||||||
final Document doc = Document.fromUri(resolver, uri);
|
final DocumentInfo doc = DocumentInfo.fromUri(resolver, uri);
|
||||||
stack.add(doc);
|
stack.add(doc);
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
@ -66,7 +65,7 @@ public class DocumentStack extends LinkedList<Document> {
|
|||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DocumentRoot getRoot(RootsCache roots) {
|
public RootInfo getRoot(RootsCache roots) {
|
||||||
return roots.findRoot(getLast().uri);
|
return roots.findRoot(getLast().uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.documentsui.model;
|
||||||
|
|
||||||
|
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
|
||||||
|
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
|
||||||
|
import static com.android.documentsui.model.DocumentInfo.getCursorString;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.provider.DocumentsContract.Root;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a {@link Root}.
|
||||||
|
*/
|
||||||
|
public class RootInfo {
|
||||||
|
public String authority;
|
||||||
|
public String rootId;
|
||||||
|
public int rootType;
|
||||||
|
public int flags;
|
||||||
|
public int icon;
|
||||||
|
public String title;
|
||||||
|
public String summary;
|
||||||
|
public String documentId;
|
||||||
|
public long availableBytes;
|
||||||
|
|
||||||
|
public static RootInfo fromRootsCursor(String authority, Cursor cursor) {
|
||||||
|
final RootInfo root = new RootInfo();
|
||||||
|
root.authority = authority;
|
||||||
|
root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID);
|
||||||
|
root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE);
|
||||||
|
root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS);
|
||||||
|
root.icon = getCursorInt(cursor, Root.COLUMN_ICON);
|
||||||
|
root.title = getCursorString(cursor, Root.COLUMN_TITLE);
|
||||||
|
root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY);
|
||||||
|
root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID);
|
||||||
|
root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable loadIcon(Context context) {
|
||||||
|
if (icon != 0) {
|
||||||
|
if (authority != null) {
|
||||||
|
final PackageManager pm = context.getPackageManager();
|
||||||
|
final ProviderInfo info = pm.resolveContentProvider(authority, 0);
|
||||||
|
if (info != null) {
|
||||||
|
return pm.getDrawable(info.packageName, icon, info.applicationInfo);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return context.getResources().getDrawable(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -26,9 +26,8 @@ import android.media.ExifInterface;
|
|||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.provider.DocumentsContract.DocumentColumns;
|
import android.provider.DocumentsContract.Document;
|
||||||
import android.provider.DocumentsContract.DocumentRoot;
|
import android.provider.DocumentsContract.Root;
|
||||||
import android.provider.DocumentsContract.Documents;
|
|
||||||
import android.provider.DocumentsProvider;
|
import android.provider.DocumentsProvider;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
@ -41,7 +40,6 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ExternalStorageProvider extends DocumentsProvider {
|
public class ExternalStorageProvider extends DocumentsProvider {
|
||||||
@ -49,36 +47,54 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
|
|
||||||
// docId format: root:path/to/file
|
// docId format: root:path/to/file
|
||||||
|
|
||||||
private static final String[] SUPPORTED_COLUMNS = new String[] {
|
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||||
DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
|
Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
|
||||||
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
|
Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
|
||||||
|
Root.COLUMN_AVAILABLE_BYTES,
|
||||||
};
|
};
|
||||||
|
|
||||||
private ArrayList<DocumentRoot> mRoots;
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||||
private HashMap<String, DocumentRoot> mTagToRoot;
|
Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
|
||||||
private HashMap<String, File> mTagToPath;
|
Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static class RootInfo {
|
||||||
|
public String rootId;
|
||||||
|
public int rootType;
|
||||||
|
public int flags;
|
||||||
|
public int icon;
|
||||||
|
public String title;
|
||||||
|
public String docId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<RootInfo> mRoots;
|
||||||
|
private HashMap<String, RootInfo> mIdToRoot;
|
||||||
|
private HashMap<String, File> mIdToPath;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
mRoots = Lists.newArrayList();
|
mRoots = Lists.newArrayList();
|
||||||
mTagToRoot = Maps.newHashMap();
|
mIdToRoot = Maps.newHashMap();
|
||||||
mTagToPath = Maps.newHashMap();
|
mIdToPath = Maps.newHashMap();
|
||||||
|
|
||||||
// TODO: support multiple storage devices
|
// TODO: support multiple storage devices
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String tag = "primary";
|
final String rootId = "primary";
|
||||||
final File path = Environment.getExternalStorageDirectory();
|
final File path = Environment.getExternalStorageDirectory();
|
||||||
mTagToPath.put(tag, path);
|
mIdToPath.put(rootId, path);
|
||||||
|
|
||||||
final DocumentRoot root = new DocumentRoot();
|
final RootInfo root = new RootInfo();
|
||||||
root.docId = getDocIdForFile(path);
|
root.rootId = "primary";
|
||||||
root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED;
|
root.rootType = Root.ROOT_TYPE_DEVICE;
|
||||||
root.title = getContext().getString(R.string.root_internal_storage);
|
root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
|
||||||
|
| Root.FLAG_PROVIDES_AUDIO | Root.FLAG_PROVIDES_VIDEO
|
||||||
|
| Root.FLAG_PROVIDES_IMAGES;
|
||||||
root.icon = R.drawable.ic_pdf;
|
root.icon = R.drawable.ic_pdf;
|
||||||
root.flags = DocumentRoot.FLAG_LOCAL_ONLY;
|
root.title = getContext().getString(R.string.root_internal_storage);
|
||||||
|
root.docId = getDocIdForFile(path);
|
||||||
mRoots.add(root);
|
mRoots.add(root);
|
||||||
mTagToRoot.put(tag, root);
|
mIdToRoot.put(rootId, root);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
@ -86,12 +102,20 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String[] resolveRootProjection(String[] projection) {
|
||||||
|
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] resolveDocumentProjection(String[] projection) {
|
||||||
|
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
|
||||||
|
}
|
||||||
|
|
||||||
private String getDocIdForFile(File file) throws FileNotFoundException {
|
private String getDocIdForFile(File file) throws FileNotFoundException {
|
||||||
String path = file.getAbsolutePath();
|
String path = file.getAbsolutePath();
|
||||||
|
|
||||||
// Find the most-specific root path
|
// Find the most-specific root path
|
||||||
Map.Entry<String, File> mostSpecific = null;
|
Map.Entry<String, File> mostSpecific = null;
|
||||||
for (Map.Entry<String, File> root : mTagToPath.entrySet()) {
|
for (Map.Entry<String, File> root : mIdToPath.entrySet()) {
|
||||||
final String rootPath = root.getValue().getPath();
|
final String rootPath = root.getValue().getPath();
|
||||||
if (path.startsWith(rootPath) && (mostSpecific == null
|
if (path.startsWith(rootPath) && (mostSpecific == null
|
||||||
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
|
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
|
||||||
@ -121,7 +145,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
final String tag = docId.substring(0, splitIndex);
|
final String tag = docId.substring(0, splitIndex);
|
||||||
final String path = docId.substring(splitIndex + 1);
|
final String path = docId.substring(splitIndex + 1);
|
||||||
|
|
||||||
File target = mTagToPath.get(tag);
|
File target = mIdToPath.get(tag);
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
throw new FileNotFoundException("No root for " + tag);
|
throw new FileNotFoundException("No root for " + tag);
|
||||||
}
|
}
|
||||||
@ -143,41 +167,48 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
flags |= Documents.FLAG_SUPPORTS_SEARCH;
|
flags |= Document.FLAG_DIR_SUPPORTS_SEARCH;
|
||||||
}
|
}
|
||||||
if (file.isDirectory() && file.canWrite()) {
|
if (file.isDirectory() && file.canWrite()) {
|
||||||
flags |= Documents.FLAG_SUPPORTS_CREATE;
|
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
}
|
}
|
||||||
if (file.canWrite()) {
|
if (file.canWrite()) {
|
||||||
flags |= Documents.FLAG_SUPPORTS_WRITE;
|
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||||
flags |= Documents.FLAG_SUPPORTS_RENAME;
|
flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||||
flags |= Documents.FLAG_SUPPORTS_DELETE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String displayName = file.getName();
|
final String displayName = file.getName();
|
||||||
final String mimeType = getTypeForFile(file);
|
final String mimeType = getTypeForFile(file);
|
||||||
if (mimeType.startsWith("image/")) {
|
if (mimeType.startsWith("image/")) {
|
||||||
flags |= Documents.FLAG_SUPPORTS_THUMBNAIL;
|
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
final RowBuilder row = result.newRow();
|
final RowBuilder row = result.newRow();
|
||||||
row.offer(DocumentColumns.DOC_ID, docId);
|
row.offer(Document.COLUMN_DOCUMENT_ID, docId);
|
||||||
row.offer(DocumentColumns.DISPLAY_NAME, displayName);
|
row.offer(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||||
row.offer(DocumentColumns.SIZE, file.length());
|
row.offer(Document.COLUMN_SIZE, file.length());
|
||||||
row.offer(DocumentColumns.MIME_TYPE, mimeType);
|
row.offer(Document.COLUMN_MIME_TYPE, mimeType);
|
||||||
row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified());
|
row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
row.offer(DocumentColumns.FLAGS, flags);
|
row.offer(Document.COLUMN_FLAGS, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DocumentRoot> getDocumentRoots() {
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
// Update free space
|
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
||||||
for (String tag : mTagToRoot.keySet()) {
|
for (String rootId : mIdToPath.keySet()) {
|
||||||
final DocumentRoot root = mTagToRoot.get(tag);
|
final RootInfo root = mIdToRoot.get(rootId);
|
||||||
final File path = mTagToPath.get(tag);
|
final File path = mIdToPath.get(rootId);
|
||||||
root.availableBytes = path.getFreeSpace();
|
|
||||||
|
final RowBuilder row = result.newRow();
|
||||||
|
row.offer(Root.COLUMN_ROOT_ID, root.rootId);
|
||||||
|
row.offer(Root.COLUMN_ROOT_TYPE, root.rootType);
|
||||||
|
row.offer(Root.COLUMN_FLAGS, root.flags);
|
||||||
|
row.offer(Root.COLUMN_ICON, root.icon);
|
||||||
|
row.offer(Root.COLUMN_TITLE, root.title);
|
||||||
|
row.offer(Root.COLUMN_DOCUMENT_ID, root.docId);
|
||||||
|
row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace());
|
||||||
}
|
}
|
||||||
return mRoots;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -187,7 +218,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
displayName = validateDisplayName(mimeType, displayName);
|
displayName = validateDisplayName(mimeType, displayName);
|
||||||
|
|
||||||
final File file = new File(parent, displayName);
|
final File file = new File(parent, displayName);
|
||||||
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
|
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||||
if (!file.mkdir()) {
|
if (!file.mkdir()) {
|
||||||
throw new IllegalStateException("Failed to mkdir " + file);
|
throw new IllegalStateException("Failed to mkdir " + file);
|
||||||
}
|
}
|
||||||
@ -203,16 +234,6 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
return getDocIdForFile(file);
|
return getDocIdForFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void renameDocument(String docId, String displayName) throws FileNotFoundException {
|
|
||||||
final File file = getFileForDocId(docId);
|
|
||||||
final File newFile = new File(file.getParentFile(), displayName);
|
|
||||||
if (!file.renameTo(newFile)) {
|
|
||||||
throw new IllegalStateException("Failed to rename " + docId);
|
|
||||||
}
|
|
||||||
// TODO: update any outstanding grants
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteDocument(String docId) throws FileNotFoundException {
|
public void deleteDocument(String docId) throws FileNotFoundException {
|
||||||
final File file = getFileForDocId(docId);
|
final File file = getFileForDocId(docId);
|
||||||
@ -222,16 +243,19 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor queryDocument(String docId) throws FileNotFoundException {
|
public Cursor queryDocument(String documentId, String[] projection)
|
||||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
throws FileNotFoundException {
|
||||||
includeFile(result, docId, null);
|
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||||
|
includeFile(result, documentId, null);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
|
public Cursor queryChildDocuments(
|
||||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
String parentDocumentId, String[] projection, String sortOrder)
|
||||||
final File parent = getFileForDocId(docId);
|
throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||||
|
final File parent = getFileForDocId(parentDocumentId);
|
||||||
for (File file : parent.listFiles()) {
|
for (File file : parent.listFiles()) {
|
||||||
includeFile(result, null, file);
|
includeFile(result, null, file);
|
||||||
}
|
}
|
||||||
@ -239,9 +263,10 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor querySearch(String docId, String query) throws FileNotFoundException {
|
public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
|
||||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
throws FileNotFoundException {
|
||||||
final File parent = getFileForDocId(docId);
|
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||||
|
final File parent = getFileForDocId(parentDocumentId);
|
||||||
|
|
||||||
final LinkedList<File> pending = new LinkedList<File>();
|
final LinkedList<File> pending = new LinkedList<File>();
|
||||||
pending.add(parent);
|
pending.add(parent);
|
||||||
@ -261,22 +286,24 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType(String docId) throws FileNotFoundException {
|
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||||
final File file = getFileForDocId(docId);
|
final File file = getFileForDocId(documentId);
|
||||||
return getTypeForFile(file);
|
return getTypeForFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
|
public ParcelFileDescriptor openDocument(
|
||||||
|
String documentId, String mode, CancellationSignal signal)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
final File file = getFileForDocId(docId);
|
final File file = getFileForDocId(documentId);
|
||||||
return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
|
return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AssetFileDescriptor openDocumentThumbnail(
|
public AssetFileDescriptor openDocumentThumbnail(
|
||||||
String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
String documentId, Point sizeHint, CancellationSignal signal)
|
||||||
final File file = getFileForDocId(docId);
|
throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocId(documentId);
|
||||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
|
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
|
||||||
file, ParcelFileDescriptor.MODE_READ_ONLY);
|
file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
|
||||||
@ -294,7 +321,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
|
|
||||||
private static String getTypeForFile(File file) {
|
private static String getTypeForFile(File file) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
return Documents.MIME_TYPE_DIR;
|
return Document.MIME_TYPE_DIR;
|
||||||
} else {
|
} else {
|
||||||
return getTypeForName(file.getName());
|
return getTypeForName(file.getName());
|
||||||
}
|
}
|
||||||
@ -314,7 +341,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String validateDisplayName(String mimeType, String displayName) {
|
private static String validateDisplayName(String mimeType, String displayName) {
|
||||||
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
|
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||||
return displayName;
|
return displayName;
|
||||||
} else {
|
} else {
|
||||||
// Try appending meaningful extension if needed
|
// Try appending meaningful extension if needed
|
||||||
|
Reference in New Issue
Block a user