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

Commit 23fdaf6f authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Add new ContentProvider for doing conversions to data streams.

This introduces basic infrastructure that should allow content
providers holding complex data to perform on-demand conversion
of their data to streams of various types.  It is achieved through
two new content provider APIs, one to interrogate the possible
stream MIME types the provider can return, and the other to
request a stream of data in a particular MIME type.

Because implementations of this will often need to do on-demand
data conversion, there is also a utility intoduced in ContentProvider
for subclasses to easily run a function to write data into a
pipe that is read by the client.

This feature is mostly intended for cut and paste and drag and
drop, as the complex data interchange allowing the source and
destination to negotiate data types and copy (possible large)
data between them.  However because it is fundamental facility
of ContentProvider, it can be used in other places, such as for
more advanced GET_CONTENT data exchanges.

An example implementation of this would be in ContactsProvider,
which can now provider a data stream when a client opens certain
pieces of it data, to return data as flat text, a vcard, or other
format.

Change-Id: I58627ea4ed359aa7cf2c66274adb18306c209cb2
parent 16393511
Loading
Loading
Loading
Loading
+222 −1
Original line number Diff line number Diff line
@@ -38871,7 +38871,7 @@
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 deprecated="deprecated"
 visibility="public"
>
</method>
@@ -39106,6 +39106,19 @@
<parameter name="uri" type="android.net.Uri">
</parameter>
</constructor>
<method name="coerceToText"
 return="java.lang.CharSequence"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="context" type="android.content.Context">
</parameter>
</method>
<method name="getIntent"
 return="android.content.Intent"
 abstract="false"
@@ -39463,6 +39476,21 @@
<parameter name="values" type="android.content.ContentValues[]">
</parameter>
</method>
<method name="compareMimeTypes"
 return="boolean"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="concreteType" type="java.lang.String">
</parameter>
<parameter name="desiredType" type="java.lang.String">
</parameter>
</method>
<method name="delete"
 return="int"
 abstract="true"
@@ -39513,6 +39541,21 @@
 visibility="public"
>
</method>
<method name="getStreamTypes"
 return="java.lang.String[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeTypeFilter" type="java.lang.String">
</parameter>
</method>
<method name="getType"
 return="java.lang.String"
 abstract="true"
@@ -39649,6 +39692,48 @@
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
<method name="openPipeHelper"
 return="android.os.ParcelFileDescriptor"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeType" type="java.lang.String">
</parameter>
<parameter name="opts" type="android.os.Bundle">
</parameter>
<parameter name="args" type="T">
</parameter>
<parameter name="func" type="android.content.ContentProvider.PipeDataWriter&lt;T&gt;">
</parameter>
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
<method name="openTypedAssetFile"
 return="android.content.res.AssetFileDescriptor"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeTypeFilter" type="java.lang.String">
</parameter>
<parameter name="opts" type="android.os.Bundle">
</parameter>
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
<method name="query"
 return="android.database.Cursor"
 abstract="true"
@@ -39740,6 +39825,35 @@
</parameter>
</method>
</class>
<interface name="ContentProvider.PipeDataWriter"
 abstract="true"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<method name="writeDataToPipe"
 return="void"
 abstract="true"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="output" type="android.os.ParcelFileDescriptor">
</parameter>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeType" type="java.lang.String">
</parameter>
<parameter name="opts" type="android.os.Bundle">
</parameter>
<parameter name="args" type="T">
</parameter>
</method>
</interface>
<class name="ContentProviderClient"
 extends="java.lang.Object"
 abstract="false"
@@ -39812,6 +39926,23 @@
 visibility="public"
>
</method>
<method name="getStreamTypes"
 return="java.lang.String[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="url" type="android.net.Uri">
</parameter>
<parameter name="mimeTypeFilter" type="java.lang.String">
</parameter>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="getType"
 return="java.lang.String"
 abstract="false"
@@ -39882,6 +40013,27 @@
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="openTypedAssetFileDescriptor"
 return="android.content.res.AssetFileDescriptor"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeType" type="java.lang.String">
</parameter>
<parameter name="opts" type="android.os.Bundle">
</parameter>
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
<exception name="RemoteException" type="android.os.RemoteException">
</exception>
</method>
<method name="query"
 return="android.database.Cursor"
 abstract="false"
@@ -40652,6 +40804,21 @@
<parameter name="authority" type="java.lang.String">
</parameter>
</method>
<method name="getStreamTypes"
 return="java.lang.String[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="url" type="android.net.Uri">
</parameter>
<parameter name="mimeTypeFilter" type="java.lang.String">
</parameter>
</method>
<method name="getSyncAdapterTypes"
 return="android.content.SyncAdapterType[]"
 abstract="false"
@@ -40849,6 +41016,25 @@
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
<method name="openTypedAssetFileDescriptor"
 return="android.content.res.AssetFileDescriptor"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="uri" type="android.net.Uri">
</parameter>
<parameter name="mimeType" type="java.lang.String">
</parameter>
<parameter name="opts" type="android.os.Bundle">
</parameter>
<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
</exception>
</method>
<method name="query"
 return="android.database.Cursor"
 abstract="false"
@@ -47262,6 +47448,17 @@
 visibility="public"
>
</field>
<field name="ACTION_PASTE"
 type="java.lang.String"
 transient="false"
 volatile="false"
 value="&quot;android.intent.action.PASTE&quot;"
 static="true"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
</field>
<field name="ACTION_PICK"
 type="java.lang.String"
 transient="false"
@@ -133933,6 +134130,19 @@
<exception name="IOException" type="java.io.IOException">
</exception>
</method>
<method name="createPipe"
 return="android.os.ParcelFileDescriptor[]"
 abstract="false"
 native="false"
 synchronized="false"
 static="true"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<exception name="IOException" type="java.io.IOException">
</exception>
</method>
<method name="describeContents"
 return="int"
 abstract="false"
@@ -213026,6 +213236,17 @@
 visibility="public"
>
</method>
<method name="getVisibleTitleHeight"
 return="int"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="getZoomControls"
 return="android.view.View"
 abstract="false"
+9 −3
Original line number Diff line number Diff line
@@ -34,6 +34,12 @@ import java.util.ArrayList;
 * You do not instantiate this class directly; instead, retrieve it through
 * {@link android.content.Context#getSystemService}.
 *
 * <p>
 * The ClipboardManager API itself is very simple: it consists of methods
 * to atomically get and set the current primary clipboard data.  That data
 * is expressed as a {@link ClippedData} object, which defines the protocol
 * for data exchange between applications.
 *
 * @see android.content.Context#getSystemService
 */
public class ClipboardManager extends android.text.ClipboardManager {
@@ -152,7 +158,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
    public CharSequence getText() {
        ClippedData clip = getPrimaryClip();
        if (clip != null && clip.getItemCount() > 0) {
            return clip.getItem(0).getText();
            return clip.getItem(0).coerceToText(mContext);
        }
        return null;
    }
@@ -167,11 +173,11 @@ public class ClipboardManager extends android.text.ClipboardManager {
    }

    /**
     * Returns true if the clipboard has a primary clip containing text; false otherwise.
     * @deprecated Use {@link #hasPrimaryClip()} instead.
     */
    public boolean hasText() {
        try {
            return getService().hasClipboardText();
            return getService().hasPrimaryClip();
        } catch (RemoteException e) {
            return false;
        }
+214 −13
Original line number Diff line number Diff line
@@ -16,12 +16,18 @@

package android.content;

import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

/**
@@ -31,7 +37,99 @@ import java.util.ArrayList;
 * each of which can hold one or more representations of an item of data.
 * For display to the user, it also has a label and iconic representation.</p>
 *
 * <p>The types than an individial item can currently contain are:</p>
 * <p>Each Item instance can be one of three main classes of data: a simple
 * CharSequence of text, a single Intent object, or a Uri.  See {@link Item}
 * for more details.
 *
 * <a name="ImplementingPaste"></a>
 * <h3>Implementing Paste or Drop</h3>
 *
 * <p>To implement a paste or drop of a ClippedData object into an application,
 * the application must correctly interpret the data for its use.  If the {@link Item}
 * it contains is simple text or an Intent, there is little to be done: text
 * can only be interpreted as text, and an Intent will typically be used for
 * creating shortcuts (such as placing icons on the home screen) or other
 * actions.
 *
 * <p>If all you want is the textual representation of the clipped data, you
 * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
 *
 * <p>More complicated exchanges will be done through URIs, in particular
 * "content:" URIs.  A content URI allows the recipient of a ClippedData item
 * to interact closely with the ContentProvider holding the data in order to
 * negotiate the transfer of that data.
 *
 * <p>For example, here is the paste function of a simple NotePad application.
 * When retrieving the data from the clipboard, it can do either two things:
 * if the clipboard contains a URI reference to an existing note, it copies
 * the entire structure of the note into a new note; otherwise, it simply
 * coerces the clip into text and uses that as the new note's contents.
 *
 * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
 *      paste}
 *
 * <p>In many cases an application can paste various types of streams of data.  For
 * example, an e-mail application may want to allow the user to paste an image
 * or other binary data as an attachment.  This is accomplished through the
 * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
 * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
 * methods.  These allow a client to discover the type(s) of data that a particular
 * content URI can make available as a stream and retrieve the stream of data.
 *
 * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
 * itself uses this to try to retrieve a URI clip as a stream of text:
 *
 * {@sample frameworks/base/core/java/android/content/ClippedData.java coerceToText}
 *
 * <a name="ImplementingCopy"></a>
 * <h3>Implementing Copy or Drag</h3>
 *
 * <p>To be the source of a clip, the application must construct a ClippedData
 * object that any recipient can interpret best for their context.  If the clip
 * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
 * containing the appropriate data type can be constructed and used.
 *
 * <p>More complicated data types require the implementation of support in
 * a ContentProvider for describing and generating the data for the recipient.
 * A common scenario is one where an application places on the clipboard the
 * content: URI of an object that the user has copied, with the data at that
 * URI consisting of a complicated structure that only other applications with
 * direct knowledge of the structure can use.
 *
 * <p>For applications that do not have intrinsic knowledge of the data structure,
 * the content provider holding it can make the data available as an arbitrary
 * number of types of data streams.  This is done by implementing the
 * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
 * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
 * methods.
 *
 * <p>Going back to our simple NotePad application, this is the implementation
 * it may have to convert a single note URI (consisting of a title and the note
 * text) into a stream of plain text data.
 *
 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
 *      stream}
 *
 * <p>The copy operation in our NotePad application is now just a simple matter
 * of making a clip containing the URI of the note being copied:
 *
 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
 *      copy}
 *
 * <p>Note if a paste operation needs this clip as text (for example to paste
 * into an editor), then {@link Item#coerceToText(Context)} will ask the content
 * provider for the clip URI as text and successfully paste the entire note.
 */
public class ClippedData implements Parcelable {
    CharSequence mLabel;
    Bitmap mIcon;

    final ArrayList<Item> mItems = new ArrayList<Item>();

    /**
     * Description of a single item in a ClippedData.
     *
     * <p>The types than an individual item can currently contain are:</p>
     *
     * <ul>
     * <li> Text: a basic string of text.  This is actually a CharSequence,
@@ -40,51 +138,154 @@ import java.util.ArrayList;
     * stripped when transporting through the clipboard.)
     * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
     * to create when pasting a clipped item on to the home screen.
 * <li> Uri: a URI reference.  Currently this should only be a content: URI.
 * This representation allows an application to share complex or large clips,
 * by providing a URI to a content provider holding the data.
     * <li> Uri: a URI reference.  This may be any URI (such as an http: URI
     * representing a bookmark), however it is often a content: URI.  Using
     * content provider references as clips like this allows an application to
     * share complex or large clips through the standard content provider
     * facilities.
     * </ul>
     */
public class ClippedData implements Parcelable {
    CharSequence mLabel;
    Bitmap mIcon;

    final ArrayList<Item> mItems = new ArrayList<Item>();

    public static class Item {
        CharSequence mText;
        Intent mIntent;
        Uri mUri;

        /**
         * Create an Item consisting of a single block of (possibly styled) text.
         */
        public Item(CharSequence text) {
            mText = text;
        }

        /**
         * Create an Item consisting of an arbitrary Intent.
         */
        public Item(Intent intent) {
            mIntent = intent;
        }

        /**
         * Create an Item consisting of an arbitrary URI.
         */
        public Item(Uri uri) {
            mUri = uri;
        }

        /**
         * Create a complex Item, containing multiple representations of
         * text, intent, and/or URI.
         */
        public Item(CharSequence text, Intent intent, Uri uri) {
            mText = text;
            mIntent = intent;
            mUri = uri;
        }

        /**
         * Retrieve the raw text contained in this Item.
         */
        public CharSequence getText() {
            return mText;
        }

        /**
         * Retrieve the raw Intent contained in this Item.
         */
        public Intent getIntent() {
            return mIntent;
        }

        /**
         * Retrieve the raw URI contained in this Item.
         */
        public Uri getUri() {
            return mUri;
        }

        /**
         * Turn this item into text, regardless of the type of data it
         * actually contains.
         *
         * <p>The algorithm for deciding what text to return is:
         * <ul>
         * <li> If {@link #getText} is non-null, return that.
         * <li> If {@link #getUri} is non-null, try to retrieve its data
         * as a text stream from its content provider.  If this succeeds, copy
         * the text into a String and return it.  If it is not a content: URI or
         * the content provider does not supply a text representation, return
         * the raw URI as a string.
         * <li> If {@link #getIntent} is non-null, convert that to an intent:
         * URI and returnit.
         * <li> Otherwise, return an empty string.
         * </ul>
         *
         * @param context The caller's Context, from which its ContentResolver
         * and other things can be retrieved.
         * @return Returns the item's textual representation.
         */
//BEGIN_INCLUDE(coerceToText)
        public CharSequence coerceToText(Context context) {
            // If this Item has an explicit textual value, simply return that.
            if (mText != null) {
                return mText;
            }

            // If this Item has a URI value, try using that.
            if (mUri != null) {

                // First see if the URI can be opened as a plain text stream
                // (of any sub-type).  If so, this is the best textual
                // representation for it.
                FileInputStream stream = null;
                try {
                    // Ask for a stream of the desired type.
                    AssetFileDescriptor descr = context.getContentResolver()
                            .openTypedAssetFileDescriptor(mUri, "text/*", null);
                    stream = descr.createInputStream();
                    InputStreamReader reader = new InputStreamReader(stream, "UTF-8");

                    // Got it...  copy the stream into a local string and return it.
                    StringBuilder builder = new StringBuilder(128);
                    char[] buffer = new char[8192];
                    int len;
                    while ((len=reader.read(buffer)) > 0) {
                        builder.append(buffer, 0, len);
                    }
                    return builder.toString();

                } catch (FileNotFoundException e) {
                    // Unable to open content URI as text...  not really an
                    // error, just something to ignore.

                } catch (IOException e) {
                    // Something bad has happened.
                    Log.w("ClippedData", "Failure loading text", e);
                    return e.toString();

                } finally {
                    if (stream != null) {
                        try {
                            stream.close();
                        } catch (IOException e) {
                        }
                    }
                }

                // If we couldn't open the URI as a stream, then the URI itself
                // probably serves fairly well as a textual representation.
                return mUri.toString();
            }

            // Finally, if all we have is an Intent, then we can just turn that
            // into text.  Not the most user-friendly thing, but it's something.
            if (mIntent != null) {
                return mIntent.toUri(Intent.URI_INTENT_SCHEME);
            }

            // Shouldn't get here, but just in case...
            return "";
        }
//END_INCLUDE(coerceToText)
    }

    /**
+177 −0

File changed.

Preview size limit exceeded, changes collapsed.

+34 −9
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.content;

import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ParcelFileDescriptor;
import android.content.res.AssetFileDescriptor;
@@ -43,53 +44,77 @@ public class ContentProviderClient {
        mContentResolver = contentResolver;
    }

    /** see {@link ContentProvider#query} */
    /** See {@link ContentProvider#query ContentProvider.query} */
    public Cursor query(Uri url, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) throws RemoteException {
        return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder);
    }

    /** see {@link ContentProvider#getType} */
    /** See {@link ContentProvider#getType ContentProvider.getType} */
    public String getType(Uri url) throws RemoteException {
        return mContentProvider.getType(url);
    }

    /** see {@link ContentProvider#insert} */
    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
    public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
        return mContentProvider.getStreamTypes(url, mimeTypeFilter);
    }

    /** See {@link ContentProvider#insert ContentProvider.insert} */
    public Uri insert(Uri url, ContentValues initialValues)
            throws RemoteException {
        return mContentProvider.insert(url, initialValues);
    }

    /** see {@link ContentProvider#bulkInsert} */
    /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
    public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
        return mContentProvider.bulkInsert(url, initialValues);
    }

    /** see {@link ContentProvider#delete} */
    /** See {@link ContentProvider#delete ContentProvider.delete} */
    public int delete(Uri url, String selection, String[] selectionArgs)
            throws RemoteException {
        return mContentProvider.delete(url, selection, selectionArgs);
    }

    /** see {@link ContentProvider#update} */
    /** See {@link ContentProvider#update ContentProvider.update} */
    public int update(Uri url, ContentValues values, String selection,
            String[] selectionArgs) throws RemoteException {
        return mContentProvider.update(url, values, selection, selectionArgs);
    }

    /** see {@link ContentProvider#openFile} */
    /**
     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
     * this <em>does not</em>
     * take care of non-content: URIs such as file:.  It is strongly recommended
     * you use the {@link ContentResolver#openFileDescriptor
     * ContentResolver.openFileDescriptor} API instead.
     */
    public ParcelFileDescriptor openFile(Uri url, String mode)
            throws RemoteException, FileNotFoundException {
        return mContentProvider.openFile(url, mode);
    }

    /** see {@link ContentProvider#openAssetFile} */
    /**
     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
     * Note that this <em>does not</em>
     * take care of non-content: URIs such as file:.  It is strongly recommended
     * you use the {@link ContentResolver#openAssetFileDescriptor
     * ContentResolver.openAssetFileDescriptor} API instead.
     */
    public AssetFileDescriptor openAssetFile(Uri url, String mode)
            throws RemoteException, FileNotFoundException {
        return mContentProvider.openAssetFile(url, mode);
    }

     /** see {@link ContentProvider#applyBatch} */
    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
            String mimeType, Bundle opts)
            throws RemoteException, FileNotFoundException {
        return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
    }

    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
            throws RemoteException, OperationApplicationException {
        return mContentProvider.applyBatch(operations);
Loading