Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ecfee03 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Start fleshing out new storage APIs.

Introduces new DocumentsContract which storage backends must
implement.  Backends surface a simple directory-like organizational
structure that enables a document to appear at multiple locations in
that hierarchy.  Querying a document or the contents of a directory
will return a Cursor populated with DocumentColumns, which includes
simple metadata.

Adds new OPEN_DOC and CREATE_DOC Intents, and permission to protect
storage backends.

Change-Id: Ib4984bc980182b2cedbe552908e5be94604ef085
parent 85c5f084
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ package android {
    field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
    field public static final java.lang.String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS";
    field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
    field public static final java.lang.String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
    field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
    field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
    field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
@@ -6047,6 +6048,7 @@ package android.content {
    field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
    field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
    field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
    field public static final java.lang.String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
    field public static final java.lang.String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
    field public static final java.lang.String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
    field public static final java.lang.String ACTION_DEFAULT = "android.intent.action.VIEW";
@@ -6091,6 +6093,7 @@ package android.content {
    field public static final java.lang.String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED";
    field public static final java.lang.String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
    field public static final java.lang.String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL";
    field public static final java.lang.String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";
    field public static final java.lang.String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED";
    field public static final java.lang.String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
    field public static final java.lang.String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
@@ -19494,6 +19497,28 @@ package android.provider {
    field public static final android.net.Uri CONTENT_URI;
  }
  public final class DocumentsContract {
    ctor public DocumentsContract();
    method public static android.net.Uri buildContentsUri(android.net.Uri);
    method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String);
    method public static android.net.Uri buildSearchUri(java.lang.String, java.lang.String);
    method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point);
    method public static boolean renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String);
    field public static final java.lang.String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
    field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
    field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2
    field public static final int FLAG_SUPPORTS_THUMBNAIL = 4; // 0x4
    field public static final java.lang.String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";
    field public static final java.lang.String ROOT_GUID = "0";
  }
  public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns {
    field public static final java.lang.String FLAGS = "flags";
    field public static final java.lang.String GUID = "guid";
    field public static final java.lang.String LAST_MODIFIED = "last_modified";
    field public static final java.lang.String MIME_TYPE = "mime_type";
  }
  public final deprecated class LiveFolders implements android.provider.BaseColumns {
    field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
    field public static final java.lang.String DESCRIPTION = "description";
+48 −0
Original line number Diff line number Diff line
@@ -2591,6 +2591,46 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";

    /**
     * Activity Action: Allow the user to select and open one or more existing
     * documents. Both read and write access to the documents will be granted
     * until explicitly revoked by the user.
     * <p>
     * Callers can restrict selection to a specific kind of data, such as
     * photos, by setting one or more MIME types in {@link #EXTRA_MIME_TYPES}.
     * <p>
     * If the caller can handle multiple returned items (the user performing
     * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE} to
     * indicate this.
     * <p>
     * All returned URIs can be opened as a stream with
     * {@link ContentResolver#openInputStream(Uri)}.
     * <p>
     * Output: The URI of the item that was picked. This must be a content: URI
     * so that any receiver can access it.
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";

    /**
     * Activity Action: Allow the user to create a new document. Both read and
     * write access to the document will be granted until explicitly revoked by
     * the user.
     * <p>
     * Callers can provide a hint document name by setting {@link #EXTRA_TITLE},
     * but the user may change this value before creating the file. Callers can
     * optionally hint at the MIME type being created by setting
     * {@link #setType(String)}.
     * <p>
     * All returned URIs can be opened as a stream with
     * {@link ContentResolver#openOutputStream(Uri)}.
     * <p>
     * Output: The URI of the item that was created. This must be a content: URI
     * so that any receiver can access it.
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";

    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Standard intent categories (see addCategory()).
@@ -3194,6 +3234,14 @@ public class Intent implements Parcelable, Cloneable {
    public static final String EXTRA_RESTRICTIONS_INTENT =
            "android.intent.extra.restrictions_intent";

    /**
     * Extra used to communicate set of acceptable MIME types for
     * {@link #ACTION_GET_CONTENT} or {@link #ACTION_OPEN_DOC}. The type of the
     * extra is <code>ArrayList&lt;String&gt;</code>.
     * @hide
     */
    public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";

    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Intent flags (see mFlags variable).
+207 −0
Original line number Diff line number Diff line
/*
 * 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 android.provider;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;

import libcore.io.IoUtils;

import java.io.IOException;
import java.io.InputStream;

/**
 * The contract between a storage backend and the platform. Contains definitions
 * for the supported URIs and columns.
 */
public final class DocumentsContract {
    private static final String TAG = "Documents";

    // content://com.example/docs/0/
    // content://com.example/docs/0/contents/
    // content://com.example/search/?query=pony

    /**
     * MIME type of a document which is a directory that may contain additional
     * documents.
     *
     * @see #buildContentsUri(Uri)
     */
    public static final String MIME_TYPE_DIRECTORY = "vnd.android.cursor.dir/doc";

    /**
     * {@link DocumentColumns#GUID} value representing the root directory of a
     * storage backend.
     */
    public static final String ROOT_GUID = "0";

    /**
     * Flag indicating that a document is a directory that supports creation of
     * new files within it.
     *
     * @see DocumentColumns#FLAGS
     * @see #buildContentsUri(Uri)
     */
    public static final int FLAG_SUPPORTS_CREATE = 1;

    /**
     * Flag indicating that a document is renamable.
     *
     * @see DocumentColumns#FLAGS
     * @see #renameDocument(ContentResolver, Uri, String)
     */
    public static final int FLAG_SUPPORTS_RENAME = 1 << 1;

    /**
     * Flag indicating that a document can be represented as a thumbnail.
     *
     * @see DocumentColumns#FLAGS
     * @see #getThumbnail(ContentResolver, Uri, Point)
     */
    public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 2;

    /**
     * Optimal dimensions for a document thumbnail request, stored as a
     * {@link Point} object. This is only a hint, and the returned thumbnail may
     * have different dimensions.
     */
    public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";

    private static final String PATH_DOCS = "docs";
    private static final String PATH_CONTENTS = "contents";
    private static final String PATH_SEARCH = "search";

    private static final String PARAM_QUERY = "query";

    /**
     * Build URI representing the given {@link DocumentColumns#GUID} in a
     * storage backend.
     */
    public static Uri buildDocumentUri(String authority, String guid) {
        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority(authority).appendPath(PATH_DOCS).appendPath(guid).build();
    }

    /**
     * Build URI representing a search for matching documents in a storage
     * backend.
     */
    public static Uri buildSearchUri(String authority, String query) {
        return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
                .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build();
    }

    /**
     * Build URI representing the contents of the given directory in a storage
     * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}.
     */
    public static Uri buildContentsUri(Uri documentUri) {
        return documentUri.buildUpon().appendPath(PATH_CONTENTS).build();
    }

    /**
     * These are standard columns for document URIs. Storage backend providers
     * <em>must</em> support at least these columns when queried.
     *
     * @see Intent#ACTION_OPEN_DOCUMENT
     * @see Intent#ACTION_CREATE_DOCUMENT
     */
    public interface DocumentColumns extends OpenableColumns {
        /**
         * The globally unique ID for a document within a storage backend.
         * Values <em>must</em> never change once returned.
         * <p>
         * Type: STRING
         *
         * @see DocumentsContract#ROOT_GUID
         */
        public static final String GUID = "guid";

        /**
         * MIME type of a document, matching the value returned by
         * {@link ContentResolver#getType(android.net.Uri)}.
         * <p>
         * Type: STRING
         *
         * @see DocumentsContract#MIME_TYPE_DIRECTORY
         */
        public static final String MIME_TYPE = "mime_type";

        /**
         * Timestamp when a document was last modified, in milliseconds since
         * January 1, 1970 00:00:00.0 UTC.
         * <p>
         * Type: INTEGER (long)
         *
         * @see System#currentTimeMillis()
         */
        public static final String LAST_MODIFIED = "last_modified";

        /**
         * Flags that apply to a specific document.
         * <p>
         * Type: INTEGER (int)
         */
        public static final String FLAGS = "flags";
    }

    /**
     * Return thumbnail representing the document at the given URI. Callers are
     * responsible for their own caching. Given document must have
     * {@link #FLAG_SUPPORTS_THUMBNAIL} set.
     *
     * @return decoded thumbnail, or {@code null} if problem was encountered.
     */
    public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
        final Bundle opts = new Bundle();
        opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size);

        InputStream is = null;
        try {
            is = new AssetFileDescriptor.AutoCloseInputStream(
                    resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts));
            return BitmapFactory.decodeStream(is);
        } catch (IOException e) {
            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
            return null;
        } finally {
            IoUtils.closeQuietly(is);
        }
    }

    /**
     * Rename the document at the given URI. Given document must have
     * {@link #FLAG_SUPPORTS_RENAME} set.
     *
     * @return if rename was successful.
     */
    public static boolean renameDocument(
            ContentResolver resolver, Uri documentUri, String displayName) {
        final ContentValues values = new ContentValues();
        values.put(DocumentColumns.DISPLAY_NAME, displayName);
        return (resolver.update(documentUri, values, null, null) == 1);
    }
}
+10 −4
Original line number Diff line number Diff line
@@ -16,11 +16,17 @@

package android.provider;

import android.content.ContentResolver;
import android.content.Intent;

/**
 * These are standard columns for openable URIs. (See
 * {@link android.content.Intent#CATEGORY_OPENABLE}.) If possible providers that have openable URIs
 * should support these columns. To find the content type of a URI use
 * {@link android.content.ContentResolver#getType(android.net.Uri)} as normal.
 * These are standard columns for openable URIs. Providers that serve openable
 * URIs <em>must</em> support at least these columns when queried.
 * <p>
 * To find the content type of a URI, use
 * {@link ContentResolver#getType(android.net.Uri)}.
 *
 * @see Intent#CATEGORY_OPENABLE
 */
public interface OpenableColumns {

+8 −0
Original line number Diff line number Diff line
@@ -1078,6 +1078,14 @@
        android:description="@string/permdesc_mediaStorageWrite"
        android:protectionLevel="signature|system" />

    <!-- Allows an application to manage access to documents, usually as part
         of a document picker. -->
    <permission android:name="android.permission.MANAGE_DOCUMENTS"
        android:permissionGroup="android.permission-group.STORAGE"
        android:label="@string/permlab_manageDocs"
        android:description="@string/permdesc_manageDocs"
        android:protectionLevel="signature|system" />

    <!-- ================================== -->
    <!-- Permissions for screenlock         -->
    <!-- ================================== -->
Loading