Merge "Refactoring of DocumentsContract." into klp-dev
This commit is contained in:
@@ -20815,67 +20815,69 @@ package android.provider {
|
||||
}
|
||||
|
||||
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 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 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_INFO = "info";
|
||||
field public static final java.lang.String EXTRA_LOADING = "loading";
|
||||
}
|
||||
|
||||
public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
|
||||
field public static final java.lang.String DOC_ID = "doc_id";
|
||||
field public static final java.lang.String FLAGS = "flags";
|
||||
field public static final java.lang.String ICON = "icon";
|
||||
field public static final java.lang.String LAST_MODIFIED = "last_modified";
|
||||
field public static final java.lang.String MIME_TYPE = "mime_type";
|
||||
field public static final java.lang.String SUMMARY = "summary";
|
||||
public static final class DocumentsContract.Document {
|
||||
field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name";
|
||||
field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
|
||||
field public static final java.lang.String COLUMN_FLAGS = "flags";
|
||||
field public static final java.lang.String COLUMN_ICON = "icon";
|
||||
field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified";
|
||||
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 {
|
||||
ctor public DocumentsContract.DocumentRoot();
|
||||
method public int describeContents();
|
||||
method public void writeToParcel(android.os.Parcel, int);
|
||||
field public static final android.os.Parcelable.Creator CREATOR;
|
||||
public static final class DocumentsContract.Root {
|
||||
field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes";
|
||||
field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id";
|
||||
field public static final java.lang.String COLUMN_FLAGS = "flags";
|
||||
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_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 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_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 {
|
||||
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 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 abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots();
|
||||
method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException;
|
||||
method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException;
|
||||
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 void notifyDocumentRootsChanged();
|
||||
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 final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException;
|
||||
@@ -20883,10 +20885,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, 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 abstract android.database.Cursor queryDocument(java.lang.String) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryDocumentChildren(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 void renameDocument(java.lang.String, 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 queryDocument(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 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[]);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package android.provider;
|
||||
import static android.net.TrafficStats.KB_IN_BYTES;
|
||||
import static libcore.io.OsConstants.SEEK_SET;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -30,16 +29,13 @@ import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ParcelFileDescriptor.OnCloseListener;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import libcore.io.ErrnoException;
|
||||
@@ -62,9 +58,12 @@ import java.util.List;
|
||||
public final class DocumentsContract {
|
||||
private static final String TAG = "Documents";
|
||||
|
||||
// content://com.example/docs/12/
|
||||
// content://com.example/docs/12/children/
|
||||
// content://com.example/docs/12/search/?query=pony
|
||||
// content://com.example/root/
|
||||
// content://com.example/root/sdcard/
|
||||
// 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() {
|
||||
}
|
||||
@@ -75,207 +74,69 @@ public final class DocumentsContract {
|
||||
/** {@hide} */
|
||||
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 {
|
||||
private Documents() {
|
||||
public final static class Document {
|
||||
private Document() {
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME type of a document which is a directory that may contain additional
|
||||
* documents.
|
||||
*/
|
||||
public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
|
||||
|
||||
/**
|
||||
* Flag indicating that a document is a directory that supports creation of
|
||||
* new files within it.
|
||||
*
|
||||
* @see DocumentColumns#FLAGS
|
||||
*/
|
||||
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.
|
||||
* Unique ID of a document. This ID is both provided by and interpreted
|
||||
* by a {@link DocumentsProvider}, and should be treated as an opaque
|
||||
* value by client applications.
|
||||
* <p>
|
||||
* Each document must have a unique ID within a provider, but that
|
||||
* single document may be included as a child of multiple directories.
|
||||
* <p>
|
||||
* A provider must always return durable IDs, since they will be used to
|
||||
* issue long-term Uri permission grants when an application interacts
|
||||
* with {@link Intent#ACTION_OPEN_DOCUMENT} and
|
||||
* {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||
* <p>
|
||||
* 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>
|
||||
* 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
|
||||
* January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
|
||||
* providers can update this field using events from
|
||||
* January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
|
||||
* {@link DocumentsProvider} can update this field using events from
|
||||
* {@link OnCloseListener} or other reliable
|
||||
* {@link ParcelFileDescriptor} transports.
|
||||
* <p>
|
||||
@@ -283,71 +144,227 @@ public final class DocumentsContract {
|
||||
*
|
||||
* @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
|
||||
* default using {@link #MIME_TYPE}.
|
||||
* Specific icon resource ID for a document, or {@code null} to use
|
||||
* platform default icon based on {@link #COLUMN_MIME_TYPE}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* Type: STRING
|
||||
*/
|
||||
public static final String SUMMARY = "summary";
|
||||
|
||||
/**
|
||||
* Flags that apply to a specific document.
|
||||
* Flags that apply to a document.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* @see #rootType
|
||||
* @see #COLUMN_ROOT_TYPE
|
||||
*/
|
||||
public static final int ROOT_TYPE_SERVICE = 1;
|
||||
|
||||
/**
|
||||
* Root that represents a shortcut to content that may be available
|
||||
* elsewhere through another storage root.
|
||||
* Type of root that represents a shortcut to content that may be
|
||||
* available elsewhere through another storage root.
|
||||
*
|
||||
* @see #rootType
|
||||
* @see #COLUMN_ROOT_TYPE
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
@@ -355,138 +372,201 @@ public final class DocumentsContract {
|
||||
* Flag indicating that this root offers content that is strictly local
|
||||
* 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;
|
||||
|
||||
/** {@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 #ROOT_TYPE_DEVICE
|
||||
* @see #COLUMN_FLAGS
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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}.
|
||||
* Flag indicating that a root offers video documents. When a user is
|
||||
* selecting video, roots not providing video may be excluded.
|
||||
*
|
||||
* @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
|
||||
* documents under this root. This directory should return at most two
|
||||
* dozen documents modified within the last 90 days. May be {@code null}
|
||||
* if this root doesn't support recents.
|
||||
* Flag indicating that a root offers image documents. When a user is
|
||||
* selecting images, roots not providing images may be excluded.
|
||||
*
|
||||
* @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
|
||||
* unbounded.
|
||||
*/
|
||||
public long availableBytes;
|
||||
/**
|
||||
* Optional 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. 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,
|
||||
* or {@code null} to indicate that all MIME types are supported. For
|
||||
* example, a provider only supporting audio and video might set this to
|
||||
* {@code ["audio/*", "video/*"]}.
|
||||
*/
|
||||
public String[] mimeTypes;
|
||||
/**
|
||||
* Optional 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";
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
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();
|
||||
if (!PATH_ROOT.equals(paths.get(0))) {
|
||||
throw new IllegalArgumentException("Not a root: " + rootUri);
|
||||
}
|
||||
return paths.get(1);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
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;
|
||||
/**
|
||||
* Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
|
||||
*/
|
||||
public static String getDocumentId(Uri documentUri) {
|
||||
final List<String> paths = documentUri.getPathSegments();
|
||||
if (paths.size() < 2) {
|
||||
throw new IllegalArgumentException("Not a document: " + documentUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
if (!PATH_DOCUMENT.equals(paths.get(0))) {
|
||||
throw new IllegalArgumentException("Not a document: " + documentUri);
|
||||
}
|
||||
return paths.get(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
Preconditions.checkNotNull(docId);
|
||||
|
||||
dest.writeInt(rootType);
|
||||
dest.writeInt(flags);
|
||||
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];
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extract the search query from a Uri built by
|
||||
* {@link #buildSearchDocumentsUri(String, String, String)}.
|
||||
*/
|
||||
public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
|
||||
return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,6 +577,7 @@ public final class DocumentsContract {
|
||||
* {@link Intent#ACTION_CREATE_DOCUMENT}.
|
||||
*
|
||||
* @see Context#grantUriPermission(String, Uri, int)
|
||||
* @see Context#revokeUriPermission(Uri, int)
|
||||
* @see ContentResolver#getIncomingUriPermissionGrants(int, int)
|
||||
*/
|
||||
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
|
||||
* responsible for their own in-memory caching. Given document must have
|
||||
* {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
|
||||
* Return thumbnail representing the document at the given Uri. Callers are
|
||||
* responsible for their own in-memory caching.
|
||||
*
|
||||
* @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.
|
||||
* @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();
|
||||
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
|
||||
|
||||
AssetFileDescriptor afd = null;
|
||||
try {
|
||||
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
|
||||
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
|
||||
|
||||
final FileDescriptor fd = afd.getFileDescriptor();
|
||||
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
|
||||
* display name.
|
||||
* Create a new document with given MIME type and 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 displayName name of new document
|
||||
* @return newly created document, or {@code null} if failed
|
||||
* @hide
|
||||
*/
|
||||
public static String createDocument(
|
||||
ContentProviderClient client, String docId, String mimeType, String displayName) {
|
||||
public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
|
||||
String mimeType, String displayName) {
|
||||
final Bundle in = new Bundle();
|
||||
in.putString(DocumentColumns.DOC_ID, docId);
|
||||
in.putString(DocumentColumns.MIME_TYPE, mimeType);
|
||||
in.putString(DocumentColumns.DISPLAY_NAME, displayName);
|
||||
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
|
||||
in.putString(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
|
||||
try {
|
||||
final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
|
||||
return out.getString(DocumentColumns.DOC_ID);
|
||||
final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
|
||||
return buildDocumentUri(
|
||||
parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to create document", e);
|
||||
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.
|
||||
*
|
||||
* @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
|
||||
* @hide
|
||||
* @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
|
||||
*/
|
||||
public static boolean deleteDocument(ContentProviderClient client, String docId) {
|
||||
public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
|
||||
final Bundle in = new Bundle();
|
||||
in.putString(DocumentColumns.DOC_ID, docId);
|
||||
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
|
||||
|
||||
try {
|
||||
client.call(METHOD_DELETE_DOCUMENT, null, in);
|
||||
final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to delete document", e);
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
|
||||
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.METHOD_CREATE_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.METHOD_GET_ROOTS;
|
||||
import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
|
||||
import static android.provider.DocumentsContract.getDocId;
|
||||
import static android.provider.DocumentsContract.getSearchQuery;
|
||||
import static android.provider.DocumentsContract.getDocumentId;
|
||||
import static android.provider.DocumentsContract.getRootId;
|
||||
import static android.provider.DocumentsContract.getSearchDocumentsQuery;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
@@ -41,15 +37,12 @@ import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ParcelFileDescriptor.OnCloseListener;
|
||||
import android.provider.DocumentsContract.DocumentColumns;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.util.Log;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* {@link DocumentColumns#DOC_ID}, which must not change once returned.
|
||||
* {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
|
||||
* <p>
|
||||
* Documents can be either an openable file (with a specific MIME type), or a
|
||||
* directory containing additional documents (with the
|
||||
* {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
|
||||
* capabilities, as described by {@link DocumentColumns#FLAGS}. The same
|
||||
* {@link DocumentColumns#DOC_ID} can be included in multiple directories.
|
||||
* {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
|
||||
* capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
|
||||
* {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
|
||||
* <p>
|
||||
* Document providers must be protected with the
|
||||
* {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
|
||||
@@ -78,22 +71,29 @@ import java.util.List;
|
||||
public abstract class DocumentsProvider extends ContentProvider {
|
||||
private static final String TAG = "DocumentsProvider";
|
||||
|
||||
private static final int MATCH_DOCUMENT = 1;
|
||||
private static final int MATCH_CHILDREN = 2;
|
||||
private static final int MATCH_SEARCH = 3;
|
||||
private static final int MATCH_ROOT = 1;
|
||||
private static final int MATCH_RECENT = 2;
|
||||
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 UriMatcher mMatcher;
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class.
|
||||
*/
|
||||
@Override
|
||||
public void attachInfo(Context context, ProviderInfo info) {
|
||||
mAuthority = info.authority;
|
||||
|
||||
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT);
|
||||
mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN);
|
||||
mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH);
|
||||
mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
|
||||
mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
|
||||
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
|
||||
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
|
||||
mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
|
||||
|
||||
// Sanity check our setup
|
||||
if (!info.exported) {
|
||||
@@ -111,83 +111,80 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all document roots provided by this document provider.
|
||||
* When this list changes, a provider must call
|
||||
* {@link #notifyDocumentRootsChanged()}.
|
||||
*/
|
||||
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.
|
||||
* Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
|
||||
* A provider must allocate a new {@link Document#COLUMN_DOCUMENT_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 displayName the display name of the new document.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public String createDocument(String docId, String mimeType, String displayName)
|
||||
public String createDocument(String documentId, String mimeType, String displayName)
|
||||
throws FileNotFoundException {
|
||||
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 displayName the new display name.
|
||||
* @param documentId the document to delete.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void renameDocument(String docId, String displayName) throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Rename not supported");
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Delete not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given document.
|
||||
*
|
||||
* @param docId the document to delete.
|
||||
*/
|
||||
public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void deleteDocument(String docId) throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Delete not supported");
|
||||
public Cursor queryRecentDocuments(String rootId, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Recent not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for the given document. A provider should avoid making
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
* the given directory.
|
||||
*
|
||||
* @param docId the directory to start search at.
|
||||
* @param parentDocumentId the directory to start search at.
|
||||
*/
|
||||
@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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
final Cursor cursor = queryDocument(docId);
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
final Cursor cursor = queryDocument(documentId, null);
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE));
|
||||
return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -233,7 +230,7 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* @param sizeHint hint of the optimal thumbnail dimensions.
|
||||
* @param signal used by the caller to signal if the request should be
|
||||
* cancelled.
|
||||
* @see Documents#FLAG_SUPPORTS_THUMBNAIL
|
||||
* @see Document#FLAG_SUPPORTS_THUMBNAIL
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
@@ -241,17 +238,31 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
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
|
||||
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
public final Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
try {
|
||||
switch (mMatcher.match(uri)) {
|
||||
case MATCH_ROOT:
|
||||
return queryRoots(projection);
|
||||
case MATCH_RECENT:
|
||||
return queryRecentDocuments(getRootId(uri), projection);
|
||||
case MATCH_DOCUMENT:
|
||||
return queryDocument(getDocId(uri));
|
||||
return queryDocument(getDocumentId(uri), projection);
|
||||
case MATCH_CHILDREN:
|
||||
return queryDocumentChildren(getDocId(uri));
|
||||
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
|
||||
case MATCH_SEARCH:
|
||||
return querySearch(getDocId(uri), getSearchQuery(uri));
|
||||
return querySearchDocuments(
|
||||
getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
|
||||
default:
|
||||
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
|
||||
public final String getType(Uri uri) {
|
||||
try {
|
||||
switch (mMatcher.match(uri)) {
|
||||
case MATCH_DOCUMENT:
|
||||
return getType(getDocId(uri));
|
||||
return getDocumentType(getDocumentId(uri));
|
||||
default:
|
||||
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
|
||||
public final Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("Insert not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class. Throws by default, and
|
||||
* cannot be overriden.
|
||||
*
|
||||
* @see #deleteDocument(String)
|
||||
*/
|
||||
@Override
|
||||
public final int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("Delete not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class. Throws by default, and
|
||||
* cannot be overriden.
|
||||
*/
|
||||
@Override
|
||||
public final int update(
|
||||
Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException("Update not supported");
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@Override
|
||||
public final Bundle callFromPackage(
|
||||
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);
|
||||
}
|
||||
|
||||
// Platform operations require the caller explicitly hold manage
|
||||
// permission; Uri permissions don't extend management operations.
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_DOCUMENTS, "Document management");
|
||||
// Require that caller can manage given document
|
||||
final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
|
||||
final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
|
||||
getContext().enforceCallingOrSelfUriPermission(
|
||||
documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
|
||||
|
||||
final Bundle out = new Bundle();
|
||||
try {
|
||||
if (METHOD_GET_ROOTS.equals(method)) {
|
||||
final List<DocumentRoot> roots = getDocumentRoots();
|
||||
out.putParcelableList(EXTRA_ROOTS, roots);
|
||||
if (METHOD_CREATE_DOCUMENT.equals(method)) {
|
||||
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
|
||||
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
|
||||
|
||||
} else if (METHOD_CREATE_DOCUMENT.equals(method)) {
|
||||
final String docId = extras.getString(DocumentColumns.DOC_ID);
|
||||
final String mimeType = extras.getString(DocumentColumns.MIME_TYPE);
|
||||
final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
|
||||
|
||||
// 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);
|
||||
// TODO: issue Uri grant towards calling package
|
||||
// TODO: enforce that package belongs to caller
|
||||
final String newDocumentId = createDocument(documentId, mimeType, displayName);
|
||||
out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
|
||||
|
||||
} 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);
|
||||
|
||||
} else {
|
||||
@@ -338,47 +363,57 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class.
|
||||
*
|
||||
* @see #openDocument(String, String, CancellationSignal)
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
|
||||
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
|
||||
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
|
||||
throws FileNotFoundException {
|
||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||
return openDocumentThumbnail(getDocId(uri), sizeHint, null);
|
||||
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
|
||||
} else {
|
||||
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class.
|
||||
*
|
||||
* @see #openDocumentThumbnail(String, Point, CancellationSignal)
|
||||
*/
|
||||
@Override
|
||||
public final AssetFileDescriptor openTypedAssetFile(
|
||||
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
|
||||
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
|
||||
return openDocumentThumbnail(getDocId(uri), sizeHint, signal);
|
||||
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
|
||||
} else {
|
||||
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.DialogFragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -28,13 +27,13 @@ import android.content.DialogInterface.OnClickListener;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.documentsui.model.Document;
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
|
||||
/**
|
||||
* Dialog to create a new directory.
|
||||
@@ -67,24 +66,17 @@ public class CreateDirectoryFragment extends DialogFragment {
|
||||
final String displayName = text1.getText().toString();
|
||||
|
||||
final DocumentsActivity activity = (DocumentsActivity) getActivity();
|
||||
final Document cwd = activity.getCurrentDirectory();
|
||||
final DocumentInfo cwd = activity.getCurrentDirectory();
|
||||
|
||||
final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
|
||||
cwd.uri.getAuthority());
|
||||
try {
|
||||
final String docId = DocumentsContract.createDocument(client,
|
||||
DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR,
|
||||
displayName);
|
||||
final Uri childUri = DocumentsContract.createDocument(
|
||||
resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
|
||||
|
||||
// Navigate into newly created child
|
||||
final Uri childUri = DocumentsContract.buildDocumentUri(
|
||||
cwd.uri.getAuthority(), docId);
|
||||
final Document childDoc = Document.fromUri(resolver, childUri);
|
||||
final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
|
||||
activity.onDocumentPicked(childDoc);
|
||||
} catch (Exception e) {
|
||||
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 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.google.android.collect.Lists;
|
||||
|
||||
@@ -81,7 +81,7 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
private AbsListView mCurrentView;
|
||||
|
||||
private Predicate<Document> mFilter;
|
||||
private Predicate<DocumentInfo> mFilter;
|
||||
|
||||
public static final int TYPE_NORMAL = 1;
|
||||
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) {
|
||||
final Uri searchUri = DocumentsContract.buildSearchUri(
|
||||
uri.getAuthority(), DocumentsContract.getDocId(uri), query);
|
||||
final Uri searchUri = DocumentsContract.buildSearchDocumentsUri(
|
||||
uri.getAuthority(), DocumentsContract.getDocumentId(uri), query);
|
||||
show(fm, TYPE_SEARCH, searchUri);
|
||||
}
|
||||
|
||||
@@ -163,21 +163,21 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
Uri contentsUri;
|
||||
if (mType == TYPE_NORMAL) {
|
||||
contentsUri = DocumentsContract.buildChildrenUri(
|
||||
uri.getAuthority(), DocumentsContract.getDocId(uri));
|
||||
contentsUri = DocumentsContract.buildChildDocumentsUri(
|
||||
uri.getAuthority(), DocumentsContract.getDocumentId(uri));
|
||||
} else if (mType == TYPE_RECENT_OPEN) {
|
||||
contentsUri = RecentsProvider.buildRecentOpen();
|
||||
} else {
|
||||
contentsUri = uri;
|
||||
}
|
||||
|
||||
final Comparator<Document> sortOrder;
|
||||
final Comparator<DocumentInfo> sortOrder;
|
||||
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) {
|
||||
sortOrder = new Document.DisplayNameComparator();
|
||||
sortOrder = new DocumentInfo.DisplayNameComparator();
|
||||
} else if (state.sortOrder == SORT_ORDER_SIZE) {
|
||||
sortOrder = new Document.SizeComparator();
|
||||
sortOrder = new DocumentInfo.SizeComparator();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown sort order " + state.sortOrder);
|
||||
}
|
||||
@@ -258,7 +258,7 @@ public class DirectoryFragment extends Fragment {
|
||||
private OnItemClickListener mItemListener = new OnItemClickListener() {
|
||||
@Override
|
||||
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)) {
|
||||
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
|
||||
}
|
||||
@@ -291,11 +291,11 @@ public class DirectoryFragment extends Fragment {
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
|
||||
final ArrayList<Document> docs = Lists.newArrayList();
|
||||
final ArrayList<DocumentInfo> docs = Lists.newArrayList();
|
||||
final int size = checked.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (checked.valueAt(i)) {
|
||||
final Document doc = mAdapter.getItem(checked.keyAt(i));
|
||||
final DocumentInfo doc = mAdapter.getItem(checked.keyAt(i));
|
||||
docs.add(doc);
|
||||
}
|
||||
}
|
||||
@@ -328,7 +328,7 @@ public class DirectoryFragment extends Fragment {
|
||||
ActionMode mode, int position, long id, boolean checked) {
|
||||
if (checked) {
|
||||
// Directories cannot be checked
|
||||
final Document doc = mAdapter.getItem(position);
|
||||
final DocumentInfo doc = mAdapter.getItem(position);
|
||||
if (doc.isDirectory()) {
|
||||
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();
|
||||
for (Document doc : docs) {
|
||||
for (DocumentInfo doc : docs) {
|
||||
uris.add(doc.uri);
|
||||
}
|
||||
|
||||
@@ -363,12 +363,12 @@ public class DirectoryFragment extends Fragment {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void onDeleteDocuments(List<Document> docs) {
|
||||
private void onDeleteDocuments(List<DocumentInfo> docs) {
|
||||
final Context context = getActivity();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
boolean hadTrouble = false;
|
||||
for (Document doc : docs) {
|
||||
for (DocumentInfo doc : docs) {
|
||||
if (!doc.isDeleteSupported()) {
|
||||
Log.w(TAG, "Skipping " + doc);
|
||||
hadTrouble = true;
|
||||
@@ -396,12 +396,12 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
|
||||
private class DocumentsAdapter extends BaseAdapter {
|
||||
private List<Document> mDocuments;
|
||||
private List<DocumentInfo> mDocuments;
|
||||
|
||||
public DocumentsAdapter() {
|
||||
}
|
||||
|
||||
public void swapDocuments(List<Document> documents) {
|
||||
public void swapDocuments(List<DocumentInfo> documents) {
|
||||
mDocuments = documents;
|
||||
|
||||
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 TextView title = (TextView) convertView.findViewById(android.R.id.title);
|
||||
@@ -507,7 +507,7 @@ public class DirectoryFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document getItem(int position) {
|
||||
public DocumentInfo getItem(int position) {
|
||||
return mDocuments.get(position);
|
||||
}
|
||||
|
||||
@@ -538,8 +538,8 @@ public class DirectoryFragment extends Fragment {
|
||||
|
||||
Bitmap result = null;
|
||||
try {
|
||||
result = DocumentsContract.getThumbnail(
|
||||
context.getContentResolver(), uri, mThumbSize);
|
||||
result = DocumentsContract.getDocumentThumbnail(
|
||||
context.getContentResolver(), uri, mThumbSize, null);
|
||||
if (result != null) {
|
||||
final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
|
||||
context, mThumbSize);
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.net.Uri;
|
||||
import android.os.CancellationSignal;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.documentsui.model.Document;
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
import com.android.internal.util.Predicate;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
@@ -41,7 +41,7 @@ import java.util.List;
|
||||
|
||||
class DirectoryResult implements AutoCloseable {
|
||||
Cursor cursor;
|
||||
List<Document> contents = Lists.newArrayList();
|
||||
List<DocumentInfo> contents = Lists.newArrayList();
|
||||
Exception e;
|
||||
|
||||
@Override
|
||||
@@ -53,11 +53,11 @@ class DirectoryResult implements AutoCloseable {
|
||||
public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
||||
|
||||
private final int mType;
|
||||
private Predicate<Document> mFilter;
|
||||
private Comparator<Document> mSortOrder;
|
||||
private Predicate<DocumentInfo> mFilter;
|
||||
private Comparator<DocumentInfo> mSortOrder;
|
||||
|
||||
public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter,
|
||||
Comparator<Document> sortOrder) {
|
||||
public DirectoryLoader(Context context, Uri uri, int type, Predicate<DocumentInfo> filter,
|
||||
Comparator<DocumentInfo> sortOrder) {
|
||||
super(context, uri);
|
||||
mType = type;
|
||||
mFilter = filter;
|
||||
@@ -84,15 +84,15 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> {
|
||||
result.cursor.registerContentObserver(mObserver);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
Document doc = null;
|
||||
DocumentInfo doc = null;
|
||||
switch (mType) {
|
||||
case TYPE_NORMAL:
|
||||
case TYPE_SEARCH:
|
||||
doc = Document.fromDirectoryCursor(uri, cursor);
|
||||
doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
|
||||
break;
|
||||
case TYPE_RECENT_OPEN:
|
||||
try {
|
||||
doc = Document.fromRecentOpenCursor(resolver, cursor);
|
||||
doc = DocumentInfo.fromRecentOpenCursor(resolver, cursor);
|
||||
} catch (FileNotFoundException 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.Context;
|
||||
import android.content.Intent;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Handles {@link DocumentRoot} changes which invalidate cached data.
|
||||
* Handles changes which invalidate cached data.
|
||||
*/
|
||||
public class DocumentChangedReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
|
||||
@@ -42,7 +42,6 @@ import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
@@ -60,8 +59,9 @@ import android.widget.SearchView.OnQueryTextListener;
|
||||
import android.widget.TextView;
|
||||
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.RootInfo;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
@@ -160,7 +160,7 @@ public class DocumentsActivity extends Activity {
|
||||
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
|
||||
final Uri rootUri = intent.getData();
|
||||
final DocumentRoot root = mRoots.findRoot(rootUri);
|
||||
final RootInfo root = mRoots.findRoot(rootUri);
|
||||
if (root != null) {
|
||||
onRootPicked(root, true);
|
||||
} else {
|
||||
@@ -252,7 +252,7 @@ public class DocumentsActivity extends Activity {
|
||||
mDrawerToggle.setDrawerIndicatorEnabled(true);
|
||||
|
||||
} else {
|
||||
final DocumentRoot root = getCurrentRoot();
|
||||
final RootInfo root = getCurrentRoot();
|
||||
actionBar.setIcon(root != null ? root.loadIcon(this) : null);
|
||||
|
||||
if (mRoots.isRecentsRoot(root)) {
|
||||
@@ -317,7 +317,7 @@ public class DocumentsActivity extends Activity {
|
||||
super.onPrepareOptionsMenu(menu);
|
||||
|
||||
final FragmentManager fm = getFragmentManager();
|
||||
final Document cwd = getCurrentDirectory();
|
||||
final DocumentInfo cwd = getCurrentDirectory();
|
||||
|
||||
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
|
||||
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) {
|
||||
return mStack.getRoot(mRoots);
|
||||
} else {
|
||||
@@ -481,7 +481,7 @@ public class DocumentsActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
public Document getCurrentDirectory() {
|
||||
public DocumentInfo getCurrentDirectory() {
|
||||
return mStack.peek();
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
private void onCurrentDirectoryChanged() {
|
||||
final FragmentManager fm = getFragmentManager();
|
||||
final Document cwd = getCurrentDirectory();
|
||||
final DocumentInfo cwd = getCurrentDirectory();
|
||||
|
||||
if (cwd == null) {
|
||||
// No directory means recents
|
||||
@@ -533,14 +533,14 @@ public class DocumentsActivity extends Activity {
|
||||
onCurrentDirectoryChanged();
|
||||
}
|
||||
|
||||
public void onRootPicked(DocumentRoot root, boolean closeDrawer) {
|
||||
public void onRootPicked(RootInfo root, boolean closeDrawer) {
|
||||
// Clear entire backstack and start in new root
|
||||
mStack.clear();
|
||||
|
||||
if (!mRoots.isRecentsRoot(root)) {
|
||||
try {
|
||||
final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId);
|
||||
onDocumentPicked(Document.fromUri(getContentResolver(), uri));
|
||||
final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
|
||||
onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri));
|
||||
} catch (FileNotFoundException e) {
|
||||
}
|
||||
} else {
|
||||
@@ -561,7 +561,7 @@ public class DocumentsActivity extends Activity {
|
||||
finish();
|
||||
}
|
||||
|
||||
public void onDocumentPicked(Document doc) {
|
||||
public void onDocumentPicked(DocumentInfo doc) {
|
||||
final FragmentManager fm = getFragmentManager();
|
||||
if (doc.isDirectory()) {
|
||||
// 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) {
|
||||
final int size = docs.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);
|
||||
}
|
||||
|
||||
public void onSaveRequested(String mimeType, String displayName) {
|
||||
final Document cwd = getCurrentDirectory();
|
||||
final DocumentInfo cwd = getCurrentDirectory();
|
||||
final String authority = cwd.uri.getAuthority();
|
||||
|
||||
final ContentProviderClient client = getContentResolver()
|
||||
.acquireUnstableContentProviderClient(authority);
|
||||
try {
|
||||
final String docId = DocumentsContract.createDocument(client,
|
||||
DocumentsContract.getDocId(cwd.uri), mimeType, displayName);
|
||||
|
||||
final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId);
|
||||
final Uri childUri = DocumentsContract.createDocument(
|
||||
getContentResolver(), cwd.uri, mimeType, displayName);
|
||||
onFinished(childUri);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
|
||||
@@ -701,7 +699,7 @@ public class DocumentsActivity extends Activity {
|
||||
|
||||
private void dumpStack() {
|
||||
Log.d(TAG, "Current stack:");
|
||||
for (Document doc : mStack) {
|
||||
for (DocumentInfo doc : mStack) {
|
||||
Log.d(TAG, "--> " + doc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package com.android.documentsui;
|
||||
|
||||
import com.android.documentsui.model.Document;
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
import com.android.internal.util.Predicate;
|
||||
|
||||
public class MimePredicate implements Predicate<Document> {
|
||||
public class MimePredicate implements Predicate<DocumentInfo> {
|
||||
private final String[] mFilters;
|
||||
|
||||
public MimePredicate(String[] filters) {
|
||||
@@ -27,7 +27,7 @@ public class MimePredicate implements Predicate<Document> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Document doc) {
|
||||
public boolean apply(DocumentInfo doc) {
|
||||
if (doc.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.text.TextUtils.TruncateAt;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -43,6 +42,7 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.documentsui.model.DocumentStack;
|
||||
import com.android.documentsui.model.RootInfo;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
@@ -181,7 +181,7 @@ public class RecentsCreateFragment extends Fragment {
|
||||
final View summaryList = convertView.findViewById(R.id.summary_list);
|
||||
|
||||
final DocumentStack stack = getItem(position);
|
||||
final DocumentRoot root = stack.getRoot(roots);
|
||||
final RootInfo root = stack.getRoot(roots);
|
||||
icon.setImageDrawable(root.loadIcon(context));
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
@@ -25,17 +25,21 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.documentsui.model.RootInfo;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.Objects;
|
||||
import com.google.android.collect.Lists;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -48,9 +52,9 @@ public class RootsCache {
|
||||
|
||||
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) {
|
||||
mContext = context;
|
||||
@@ -66,12 +70,10 @@ public class RootsCache {
|
||||
|
||||
{
|
||||
// Create special root for recents
|
||||
final DocumentRoot root = new DocumentRoot();
|
||||
root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT;
|
||||
root.docId = null;
|
||||
final RootInfo root = new RootInfo();
|
||||
root.rootType = Root.ROOT_TYPE_SHORTCUT;
|
||||
root.icon = R.drawable.ic_dir;
|
||||
root.title = mContext.getString(R.string.root_recent);
|
||||
root.summary = null;
|
||||
root.availableBytes = -1;
|
||||
|
||||
mRoots.add(root);
|
||||
@@ -89,28 +91,32 @@ public class RootsCache {
|
||||
|
||||
// TODO: remove deprecated customRoots flag
|
||||
// TODO: populate roots on background thread, and cache results
|
||||
final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
|
||||
final ContentProviderClient client = resolver
|
||||
.acquireUnstableContentProviderClient(info.authority);
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client);
|
||||
for (DocumentRoot root : roots) {
|
||||
root.authority = info.authority;
|
||||
cursor = client.query(rootsUri, null, null, null, null);
|
||||
while (cursor.moveToNext()) {
|
||||
final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
|
||||
mRoots.add(root);
|
||||
}
|
||||
mRoots.addAll(roots);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(cursor);
|
||||
ContentProviderClient.closeQuietly(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentRoot findRoot(Uri uri) {
|
||||
@Deprecated
|
||||
public RootInfo findRoot(Uri uri) {
|
||||
final String authority = uri.getAuthority();
|
||||
final String docId = DocumentsContract.getDocId(uri);
|
||||
for (DocumentRoot root : mRoots) {
|
||||
if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
for (RootInfo root : mRoots) {
|
||||
if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -118,23 +124,23 @@ public class RootsCache {
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
public DocumentRoot getRecentsRoot() {
|
||||
public RootInfo getRecentsRoot() {
|
||||
return mRecentsRoot;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
public boolean isRecentsRoot(DocumentRoot root) {
|
||||
public boolean isRecentsRoot(RootInfo root) {
|
||||
return mRecentsRoot == root;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
public List<DocumentRoot> getRoots() {
|
||||
public List<RootInfo> getRoots() {
|
||||
return mRoots;
|
||||
}
|
||||
|
||||
@GuardedBy("ActivityThread")
|
||||
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);
|
||||
} else {
|
||||
final PackageManager pm = context.getPackageManager();
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -40,7 +40,8 @@ import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.List;
|
||||
@@ -101,8 +102,8 @@ public class RootsFragment extends Fragment {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
|
||||
final Object item = mAdapter.getItem(position);
|
||||
if (item instanceof DocumentRoot) {
|
||||
activity.onRootPicked((DocumentRoot) item, true);
|
||||
if (item instanceof RootInfo) {
|
||||
activity.onRootPicked((RootInfo) item, true);
|
||||
} else if (item instanceof ResolveInfo) {
|
||||
activity.onAppPicked((ResolveInfo) item);
|
||||
} 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;
|
||||
|
||||
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 summary = (TextView) convertView.findViewById(android.R.id.summary);
|
||||
|
||||
final DocumentRoot root = getItem(position);
|
||||
final RootInfo root = getItem(position);
|
||||
icon.setImageDrawable(root.loadIcon(context));
|
||||
title.setText(root.title);
|
||||
|
||||
// Device summary is always available space
|
||||
final String summaryText;
|
||||
if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE
|
||||
|| root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED)
|
||||
&& root.availableBytes >= 0) {
|
||||
if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
|
||||
summaryText = context.getString(R.string.root_available_bytes,
|
||||
Formatter.formatFileSize(context, root.availableBytes));
|
||||
} else {
|
||||
@@ -215,28 +214,27 @@ public class RootsFragment extends Fragment {
|
||||
private final RootsAdapter mDevicesAdvanced;
|
||||
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);
|
||||
mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut);
|
||||
mDevices = new RootsAdapter(context, R.string.root_type_device);
|
||||
mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device);
|
||||
mApps = new AppsAdapter(context);
|
||||
|
||||
for (DocumentRoot root : roots) {
|
||||
for (RootInfo root : roots) {
|
||||
Log.d(TAG, "Found rootType=" + root.rootType);
|
||||
switch (root.rootType) {
|
||||
case DocumentRoot.ROOT_TYPE_SERVICE:
|
||||
case Root.ROOT_TYPE_SERVICE:
|
||||
mServices.add(root);
|
||||
break;
|
||||
case DocumentRoot.ROOT_TYPE_SHORTCUT:
|
||||
case Root.ROOT_TYPE_SHORTCUT:
|
||||
mShortcuts.add(root);
|
||||
break;
|
||||
case DocumentRoot.ROOT_TYPE_DEVICE:
|
||||
mDevices.add(root);
|
||||
mDevicesAdvanced.add(root);
|
||||
break;
|
||||
case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED:
|
||||
case Root.ROOT_TYPE_DEVICE:
|
||||
mDevicesAdvanced.add(root);
|
||||
if ((root.flags & Root.FLAG_ADVANCED) == 0) {
|
||||
mDevices.add(root);
|
||||
}
|
||||
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
|
||||
public int compare(DocumentRoot lhs, DocumentRoot rhs) {
|
||||
final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title);
|
||||
public int compare(RootInfo lhs, RootInfo rhs) {
|
||||
final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title);
|
||||
if (score != 0) {
|
||||
return score;
|
||||
} 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.ImageView;
|
||||
|
||||
import com.android.documentsui.model.Document;
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
|
||||
/**
|
||||
* Display document title editor and save button.
|
||||
@@ -39,7 +39,7 @@ import com.android.documentsui.model.Document;
|
||||
public class SaveFragment extends Fragment {
|
||||
public static final String TAG = "SaveFragment";
|
||||
|
||||
private Document mReplaceTarget;
|
||||
private DocumentInfo mReplaceTarget;
|
||||
private EditText mDisplayName;
|
||||
private Button mSave;
|
||||
private boolean mIgnoreNextEdit;
|
||||
@@ -128,7 +128,7 @@ public class SaveFragment extends Fragment {
|
||||
* without changing the filename. Can be set to {@code null} if user
|
||||
* navigates outside the target directory.
|
||||
*/
|
||||
public void setReplaceTarget(Document replaceTarget) {
|
||||
public void setReplaceTarget(DocumentInfo replaceTarget) {
|
||||
mReplaceTarget = replaceTarget;
|
||||
|
||||
if (mReplaceTarget != null) {
|
||||
|
||||
@@ -20,8 +20,7 @@ import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.DocumentColumns;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
|
||||
import com.android.documentsui.RecentsProvider;
|
||||
|
||||
@@ -31,9 +30,9 @@ import java.io.FileNotFoundException;
|
||||
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 String mimeType;
|
||||
public final String displayName;
|
||||
@@ -42,7 +41,7 @@ public class Document {
|
||||
public final String summary;
|
||||
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) {
|
||||
this.uri = uri;
|
||||
this.mimeType = mimeType;
|
||||
@@ -53,23 +52,23 @@ public class Document {
|
||||
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 docId = getCursorString(cursor, DocumentColumns.DOC_ID);
|
||||
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
|
||||
|
||||
final Uri uri = DocumentsContract.buildDocumentUri(authority, docId);
|
||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
||||
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
|
||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
|
||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
||||
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
|
||||
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||
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
|
||||
public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
|
||||
public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor)
|
||||
throws FileNotFoundException {
|
||||
final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI));
|
||||
final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP);
|
||||
@@ -80,14 +79,14 @@ public class Document {
|
||||
if (!cursor.moveToFirst()) {
|
||||
throw new FileNotFoundException("Missing details for " + uri);
|
||||
}
|
||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS)
|
||||
& Documents.FLAG_SUPPORTS_THUMBNAIL;
|
||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
||||
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS)
|
||||
& Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||
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) {
|
||||
throw asFileNotFoundException(t);
|
||||
} 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;
|
||||
try {
|
||||
cursor = resolver.query(uri, null, null, null, null);
|
||||
if (!cursor.moveToFirst()) {
|
||||
throw new FileNotFoundException("Missing details for " + uri);
|
||||
}
|
||||
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
|
||||
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
|
||||
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
|
||||
final String summary = getCursorString(cursor, DocumentColumns.SUMMARY);
|
||||
final long size = getCursorLong(cursor, DocumentColumns.SIZE);
|
||||
final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
|
||||
final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
|
||||
final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
|
||||
final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
|
||||
final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
|
||||
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) {
|
||||
throw asFileNotFoundException(t);
|
||||
} finally {
|
||||
@@ -123,30 +122,30 @@ public class Document {
|
||||
}
|
||||
|
||||
public boolean isCreateSupported() {
|
||||
return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0;
|
||||
return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
|
||||
}
|
||||
|
||||
public boolean isSearchSupported() {
|
||||
return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0;
|
||||
return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0;
|
||||
}
|
||||
|
||||
public boolean isThumbnailSupported() {
|
||||
return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
||||
return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
|
||||
}
|
||||
|
||||
public boolean isDirectory() {
|
||||
return Documents.MIME_TYPE_DIR.equals(mimeType);
|
||||
return Document.MIME_TYPE_DIR.equals(mimeType);
|
||||
}
|
||||
|
||||
public boolean isGridPreferred() {
|
||||
return (flags & Documents.FLAG_PREFERS_GRID) != 0;
|
||||
return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
|
||||
}
|
||||
|
||||
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);
|
||||
return (index != -1) ? cursor.getString(index) : null;
|
||||
}
|
||||
@@ -154,7 +153,7 @@ public class Document {
|
||||
/**
|
||||
* 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);
|
||||
if (index == -1) return -1;
|
||||
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);
|
||||
return (index != -1) ? cursor.getInt(index) : 0;
|
||||
}
|
||||
|
||||
public static class DisplayNameComparator implements Comparator<Document> {
|
||||
public static class DisplayNameComparator implements Comparator<DocumentInfo> {
|
||||
@Override
|
||||
public int compare(Document lhs, Document rhs) {
|
||||
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||
final boolean leftDir = lhs.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
|
||||
public int compare(Document lhs, Document rhs) {
|
||||
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||
return Long.compare(rhs.lastModified, lhs.lastModified);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SizeComparator implements Comparator<Document> {
|
||||
public static class SizeComparator implements Comparator<DocumentInfo> {
|
||||
@Override
|
||||
public int compare(Document lhs, Document rhs) {
|
||||
public int compare(DocumentInfo lhs, DocumentInfo rhs) {
|
||||
return Long.compare(rhs.size, lhs.size);
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,10 @@
|
||||
package com.android.documentsui.model;
|
||||
|
||||
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.net.Uri;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.documentsui.RootsCache;
|
||||
@@ -33,10 +32,10 @@ import java.io.FileNotFoundException;
|
||||
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.
|
||||
*/
|
||||
public class DocumentStack extends LinkedList<Document> {
|
||||
public class DocumentStack extends LinkedList<DocumentInfo> {
|
||||
|
||||
public static String serialize(DocumentStack stack) {
|
||||
final JSONArray json = new JSONArray();
|
||||
@@ -55,7 +54,7 @@ public class DocumentStack extends LinkedList<Document> {
|
||||
final JSONArray json = new JSONArray(raw);
|
||||
for (int i = 0; i < json.length(); 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);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
@@ -66,7 +65,7 @@ public class DocumentStack extends LinkedList<Document> {
|
||||
return stack;
|
||||
}
|
||||
|
||||
public DocumentRoot getRoot(RootsCache roots) {
|
||||
public RootInfo getRoot(RootsCache roots) {
|
||||
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.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.DocumentColumns;
|
||||
import android.provider.DocumentsContract.DocumentRoot;
|
||||
import android.provider.DocumentsContract.Documents;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
@@ -41,7 +40,6 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExternalStorageProvider extends DocumentsProvider {
|
||||
@@ -49,36 +47,54 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
|
||||
// docId format: root:path/to/file
|
||||
|
||||
private static final String[] SUPPORTED_COLUMNS = new String[] {
|
||||
DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE,
|
||||
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||
Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
|
||||
Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES,
|
||||
};
|
||||
|
||||
private ArrayList<DocumentRoot> mRoots;
|
||||
private HashMap<String, DocumentRoot> mTagToRoot;
|
||||
private HashMap<String, File> mTagToPath;
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||
Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
|
||||
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
|
||||
public boolean onCreate() {
|
||||
mRoots = Lists.newArrayList();
|
||||
mTagToRoot = Maps.newHashMap();
|
||||
mTagToPath = Maps.newHashMap();
|
||||
mIdToRoot = Maps.newHashMap();
|
||||
mIdToPath = Maps.newHashMap();
|
||||
|
||||
// TODO: support multiple storage devices
|
||||
|
||||
try {
|
||||
final String tag = "primary";
|
||||
final String rootId = "primary";
|
||||
final File path = Environment.getExternalStorageDirectory();
|
||||
mTagToPath.put(tag, path);
|
||||
mIdToPath.put(rootId, path);
|
||||
|
||||
final DocumentRoot root = new DocumentRoot();
|
||||
root.docId = getDocIdForFile(path);
|
||||
root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED;
|
||||
root.title = getContext().getString(R.string.root_internal_storage);
|
||||
final RootInfo root = new RootInfo();
|
||||
root.rootId = "primary";
|
||||
root.rootType = Root.ROOT_TYPE_DEVICE;
|
||||
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.flags = DocumentRoot.FLAG_LOCAL_ONLY;
|
||||
root.title = getContext().getString(R.string.root_internal_storage);
|
||||
root.docId = getDocIdForFile(path);
|
||||
mRoots.add(root);
|
||||
mTagToRoot.put(tag, root);
|
||||
mIdToRoot.put(rootId, root);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
@@ -86,12 +102,20 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
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 {
|
||||
String path = file.getAbsolutePath();
|
||||
|
||||
// Find the most-specific root path
|
||||
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();
|
||||
if (path.startsWith(rootPath) && (mostSpecific == null
|
||||
|| rootPath.length() > mostSpecific.getValue().getPath().length())) {
|
||||
@@ -121,7 +145,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
final String tag = docId.substring(0, splitIndex);
|
||||
final String path = docId.substring(splitIndex + 1);
|
||||
|
||||
File target = mTagToPath.get(tag);
|
||||
File target = mIdToPath.get(tag);
|
||||
if (target == null) {
|
||||
throw new FileNotFoundException("No root for " + tag);
|
||||
}
|
||||
@@ -143,41 +167,48 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
int flags = 0;
|
||||
|
||||
if (file.isDirectory()) {
|
||||
flags |= Documents.FLAG_SUPPORTS_SEARCH;
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_SEARCH;
|
||||
}
|
||||
if (file.isDirectory() && file.canWrite()) {
|
||||
flags |= Documents.FLAG_SUPPORTS_CREATE;
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
if (file.canWrite()) {
|
||||
flags |= Documents.FLAG_SUPPORTS_WRITE;
|
||||
flags |= Documents.FLAG_SUPPORTS_RENAME;
|
||||
flags |= Documents.FLAG_SUPPORTS_DELETE;
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||
flags |= Document.FLAG_SUPPORTS_DELETE;
|
||||
}
|
||||
|
||||
final String displayName = file.getName();
|
||||
final String mimeType = getTypeForFile(file);
|
||||
if (mimeType.startsWith("image/")) {
|
||||
flags |= Documents.FLAG_SUPPORTS_THUMBNAIL;
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
}
|
||||
|
||||
final RowBuilder row = result.newRow();
|
||||
row.offer(DocumentColumns.DOC_ID, docId);
|
||||
row.offer(DocumentColumns.DISPLAY_NAME, displayName);
|
||||
row.offer(DocumentColumns.SIZE, file.length());
|
||||
row.offer(DocumentColumns.MIME_TYPE, mimeType);
|
||||
row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified());
|
||||
row.offer(DocumentColumns.FLAGS, flags);
|
||||
row.offer(Document.COLUMN_DOCUMENT_ID, docId);
|
||||
row.offer(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||
row.offer(Document.COLUMN_SIZE, file.length());
|
||||
row.offer(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.offer(Document.COLUMN_FLAGS, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DocumentRoot> getDocumentRoots() {
|
||||
// Update free space
|
||||
for (String tag : mTagToRoot.keySet()) {
|
||||
final DocumentRoot root = mTagToRoot.get(tag);
|
||||
final File path = mTagToPath.get(tag);
|
||||
root.availableBytes = path.getFreeSpace();
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
||||
for (String rootId : mIdToPath.keySet()) {
|
||||
final RootInfo root = mIdToRoot.get(rootId);
|
||||
final File path = mIdToPath.get(rootId);
|
||||
|
||||
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
|
||||
@@ -187,7 +218,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
displayName = validateDisplayName(mimeType, 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()) {
|
||||
throw new IllegalStateException("Failed to mkdir " + file);
|
||||
}
|
||||
@@ -203,16 +234,6 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
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
|
||||
public void deleteDocument(String docId) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(docId);
|
||||
@@ -222,16 +243,19 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String docId) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
||||
includeFile(result, docId, null);
|
||||
public Cursor queryDocument(String documentId, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||
includeFile(result, documentId, null);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocumentChildren(String docId) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
||||
final File parent = getFileForDocId(docId);
|
||||
public Cursor queryChildDocuments(
|
||||
String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
for (File file : parent.listFiles()) {
|
||||
includeFile(result, null, file);
|
||||
}
|
||||
@@ -239,9 +263,10 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor querySearch(String docId, String query) throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS);
|
||||
final File parent = getFileForDocId(docId);
|
||||
public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
|
||||
throws FileNotFoundException {
|
||||
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||
final File parent = getFileForDocId(parentDocumentId);
|
||||
|
||||
final LinkedList<File> pending = new LinkedList<File>();
|
||||
pending.add(parent);
|
||||
@@ -261,22 +286,24 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(String docId) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(docId);
|
||||
public String getDocumentType(String documentId) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
return getTypeForFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
|
||||
public ParcelFileDescriptor openDocument(
|
||||
String documentId, String mode, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(docId);
|
||||
final File file = getFileForDocId(documentId);
|
||||
return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(
|
||||
String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
|
||||
final File file = getFileForDocId(docId);
|
||||
String documentId, Point sizeHint, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
|
||||
file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
|
||||
@@ -294,7 +321,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
|
||||
private static String getTypeForFile(File file) {
|
||||
if (file.isDirectory()) {
|
||||
return Documents.MIME_TYPE_DIR;
|
||||
return Document.MIME_TYPE_DIR;
|
||||
} else {
|
||||
return getTypeForName(file.getName());
|
||||
}
|
||||
@@ -314,7 +341,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
}
|
||||
|
||||
private static String validateDisplayName(String mimeType, String displayName) {
|
||||
if (Documents.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||
return displayName;
|
||||
} else {
|
||||
// Try appending meaningful extension if needed
|
||||
|
||||
Reference in New Issue
Block a user