summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.xml223
-rw-r--r--core/java/android/content/ClipboardManager.java12
-rw-r--r--core/java/android/content/ClippedData.java227
-rw-r--r--core/java/android/content/ContentProvider.java177
-rw-r--r--core/java/android/content/ContentProviderClient.java43
-rw-r--r--core/java/android/content/ContentProviderNative.java77
-rw-r--r--core/java/android/content/ContentResolver.java153
-rw-r--r--core/java/android/content/IContentProvider.java8
-rw-r--r--core/java/android/content/Intent.java9
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java8
-rw-r--r--core/java/android/os/AsyncTask.java5
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java19
-rw-r--r--core/java/android/widget/TextView.java53
-rw-r--r--core/jni/android_os_ParcelFileDescriptor.cpp22
-rw-r--r--test-runner/src/android/test/mock/MockContentProvider.java18
-rw-r--r--test-runner/src/android/test/mock/MockIContentProvider.java10
16 files changed, 998 insertions, 66 deletions
diff --git a/api/current.xml b/api/current.xml
index 3e32be2ac01d..61c1cacb59c8 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -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"
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 5371fa5b29c0..d685cf37dde5 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -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;
}
diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClippedData.java
index ebb194f3b55b..c3f0237eb36d 100644
--- a/core/java/android/content/ClippedData.java
+++ b/core/java/android/content/ClippedData.java
@@ -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,19 +37,88 @@ 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>
- *
- * <ul>
- * <li> Text: a basic string of text. This is actually a CharSequence,
- * so it can be formatted text supported by corresponding Android built-in
- * style spans. (Custom application spans are not supported and will be
- * 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.
- * </ul>
+ * <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;
@@ -51,40 +126,166 @@ public class ClippedData implements Parcelable {
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,
+ * so it can be formatted text supported by corresponding Android built-in
+ * style spans. (Custom application spans are not supported and will be
+ * 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. 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 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)
}
/**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index a3252ed2f40c..1163add3a4d4 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -28,6 +28,7 @@ import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.database.SQLException;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -36,6 +37,7 @@ import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -251,6 +253,18 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.call(method, request, args);
}
+ @Override
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ return ContentProvider.this.getStreamTypes(uri, mimeTypeFilter);
+ }
+
+ @Override
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
+ throws FileNotFoundException {
+ enforceReadPermission(uri);
+ return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
+ }
+
private void enforceReadPermission(Uri uri) {
final int uid = Binder.getCallingUid();
if (uid == mMyUid) {
@@ -752,6 +766,164 @@ public abstract class ContentProvider implements ComponentCallbacks {
}
/**
+ * Helper to compare two MIME types, where one may be a pattern.
+ * @param concreteType A fully-specified MIME type.
+ * @param desiredType A desired MIME type that may be a pattern such as *\/*.
+ * @return Returns true if the two MIME types match.
+ */
+ public static boolean compareMimeTypes(String concreteType, String desiredType) {
+ final int typeLength = desiredType.length();
+ if (typeLength == 3 && desiredType.equals("*/*")) {
+ return true;
+ }
+
+ final int slashpos = desiredType.indexOf('/');
+ if (slashpos > 0) {
+ if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+ if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+ return true;
+ }
+ } else if (desiredType.equals(concreteType)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Called by a client to determine the types of data streams that this
+ * content provider supports for the given URI. The default implementation
+ * returns null, meaning no types. If your content provider stores data
+ * of a particular type, return that MIME type if it matches the given
+ * mimeTypeFilter. If it can perform type conversions, return an array
+ * of all supported MIME types that match mimeTypeFilter.
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as *\/* to retrieve all possible data types.
+ * @returns Returns null if there are no possible data streams for the
+ * given mimeTypeFilter. Otherwise returns an array of all available
+ * concrete MIME types.
+ *
+ * @see #getType(Uri)
+ * @see #openTypedAssetFile(Uri, String, Bundle)
+ * @see #compareMimeTypes(String, String)
+ */
+ public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+ return null;
+ }
+
+ /**
+ * Called by a client to open a read-only stream containing data of a
+ * particular MIME type. This is like {@link #openAssetFile(Uri, String)},
+ * except the file can only be read-only and the content provider may
+ * perform data conversions to generate data of the desired type.
+ *
+ * <p>The default implementation compares the given mimeType against the
+ * result of {@link #getType(Uri)} and, if the match, simple calls
+ * {@link #openAssetFile(Uri, String)}.
+ *
+ * <p>See {@link ClippedData} for examples of the use and implementation
+ * of this method.
+ *
+ * @param uri The data in the content provider being queried.
+ * @param mimeTypeFilter The type of data the client desires. May be
+ * a pattern, such as *\/*, if the caller does not have specific type
+ * requirements; in this case the content provider will pick its best
+ * type matching the pattern.
+ * @param opts Additional options from the client. The definitions of
+ * these are specific to the content provider being called.
+ *
+ * @return Returns a new AssetFileDescriptor from which the client can
+ * read data of the desired type.
+ *
+ * @throws FileNotFoundException Throws FileNotFoundException if there is
+ * no file associated with the given URI or the mode is invalid.
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not have permission to access the data.
+ * @throws IllegalArgumentException Throws IllegalArgumentException if the
+ * content provider does not support the requested MIME type.
+ *
+ * @see #getStreamTypes(Uri, String)
+ * @see #openAssetFile(Uri, String)
+ * @see #compareMimeTypes(String, String)
+ */
+ public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+ throws FileNotFoundException {
+ String baseType = getType(uri);
+ if (baseType != null && compareMimeTypes(baseType, mimeTypeFilter)) {
+ return openAssetFile(uri, "r");
+ }
+ throw new FileNotFoundException("Can't open " + uri + " as type " + mimeTypeFilter);
+ }
+
+ /**
+ * Interface to write a stream of data to a pipe. Use with
+ * {@link ContentProvider#openPipeHelper}.
+ */
+ public interface PipeDataWriter<T> {
+ /**
+ * Called from a background thread to stream data out to a pipe.
+ * Note that the pipe is blocking, so this thread can block on
+ * writes for an arbitrary amount of time if the client is slow
+ * at reading.
+ *
+ * @param output The pipe where data should be written. This will be
+ * closed for you upon returning from this function.
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ */
+ public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
+ Bundle opts, T args);
+ }
+
+ /**
+ * A helper function for implementing {@link #openTypedAssetFile}, for
+ * creating a data pipe and background thread allowing you to stream
+ * generated data back to the client. This function returns a new
+ * ParcelFileDescriptor that should be returned to the caller (the caller
+ * is responsible for closing it).
+ *
+ * @param uri The URI whose data is to be written.
+ * @param mimeType The desired type of data to be written.
+ * @param opts Options supplied by caller.
+ * @param args Your own custom arguments.
+ * @param func Interface implementing the function that will actually
+ * stream the data.
+ * @return Returns a new ParcelFileDescriptor holding the read side of
+ * the pipe. This should be returned to the caller for reading; the caller
+ * is responsible for closing it when done.
+ */
+ public <T> ParcelFileDescriptor openPipeHelper(final Uri uri, final String mimeType,
+ final Bundle opts, final T args, final PipeDataWriter<T> func)
+ throws FileNotFoundException {
+ try {
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+
+ AsyncTask<Object, Object, Object> task = new AsyncTask<Object, Object, Object>() {
+ @Override
+ protected Object doInBackground(Object... params) {
+ func.writeDataToPipe(fds[1], uri, mimeType, opts, args);
+ try {
+ fds[1].close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failure closing pipe", e);
+ }
+ return null;
+ }
+ };
+ task.execute((Object[])null);
+
+ return fds[0];
+ } catch (IOException e) {
+ throw new FileNotFoundException("failure making pipe");
+ }
+ }
+
+ /**
* Returns true if this instance is a temporary content provider.
* @return true if this instance is a temporary content provider
*/
@@ -777,6 +949,11 @@ public abstract class ContentProvider implements ComponentCallbacks {
* @param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
+ /*
+ * We may be using AsyncTask from binder threads. Make it init here
+ * so its static handler is on the main thread.
+ */
+ AsyncTask.init();
/*
* Only allow it to be set once, so after the content service gives
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0858ea51c76c..0540109b4f91 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -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);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index fdb3d20c9f99..d1ab8d576412 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -257,6 +257,38 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
reply.writeBundle(responseBundle);
return true;
}
+
+ case GET_STREAM_TYPES_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeTypeFilter = data.readString();
+ String[] types = getStreamTypes(url, mimeTypeFilter);
+ reply.writeNoException();
+ reply.writeStringArray(types);
+
+ return true;
+ }
+
+ case OPEN_TYPED_ASSET_FILE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ Uri url = Uri.CREATOR.createFromParcel(data);
+ String mimeType = data.readString();
+ Bundle opts = data.readBundle();
+
+ AssetFileDescriptor fd;
+ fd = openTypedAssetFile(url, mimeType, opts);
+ reply.writeNoException();
+ if (fd != null) {
+ reply.writeInt(1);
+ fd.writeToParcel(reply,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -568,5 +600,50 @@ final class ContentProviderProxy implements IContentProvider
return bundle;
}
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeTypeFilter);
+
+ mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String[] out = reply.createStringArray();
+
+ data.recycle();
+ reply.recycle();
+
+ return out;
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeType);
+ data.writeBundle(opts);
+
+ mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+
+ data.recycle();
+ reply.recycle();
+
+ return fd;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2ea0df967881..22feb9a984a6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -186,8 +186,7 @@ public abstract class ContentResolver {
* using the content:// scheme.
* @return A MIME type for the content, or null if the URL is invalid or the type is unknown
*/
- public final String getType(Uri url)
- {
+ public final String getType(Uri url) {
IContentProvider provider = acquireProvider(url);
if (provider == null) {
return null;
@@ -204,6 +203,39 @@ public abstract class ContentResolver {
}
/**
+ * Query for the possible MIME types for the representations the given
+ * content URL can be returned when opened as as stream with
+ * {@link #openTypedAssetFileDescriptor}. Note that the types here are
+ * not necessarily a superset of the type returned by {@link #getType} --
+ * many content providers can not return a raw stream for the structured
+ * data that they contain.
+ *
+ * @param url A Uri identifying content (either a list or specific type),
+ * using the content:// scheme.
+ * @param mimeTypeFilter The desired MIME type. This may be a pattern,
+ * such as *\/*, to query for all available MIME types that match the
+ * pattern.
+ * @return Returns an array of MIME type strings for all availablle
+ * data streams that match the given mimeTypeFilter. If there are none,
+ * null is returned.
+ */
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+ try {
+ return provider.getStreamTypes(url, mimeTypeFilter);
+ } catch (RemoteException e) {
+ return null;
+ } catch (java.lang.Exception e) {
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
* <p>
* Query the given URI, returning a {@link Cursor} over the result set.
* </p>
@@ -349,7 +381,7 @@ public abstract class ContentResolver {
}
/**
- * Open a raw file descriptor to access data under a "content:" URI. This
+ * Open a raw file descriptor to access data under a URI. This
* is like {@link #openAssetFileDescriptor(Uri, String)}, but uses the
* underlying {@link ContentProvider#openFile}
* ContentProvider.openFile()} method, so will <em>not</em> work with
@@ -399,7 +431,7 @@ public abstract class ContentResolver {
}
/**
- * Open a raw file descriptor to access data under a "content:" URI. This
+ * Open a raw file descriptor to access data under a URI. This
* interacts with the underlying {@link ContentProvider#openAssetFile}
* method of the provider associated with the given URI, to retrieve any file stored there.
*
@@ -433,6 +465,11 @@ public abstract class ContentResolver {
* </li>
* </ul>
*
+ * <p>Note that if this function is called for read-only input (mode is "r")
+ * on a content: URI, it will instead call {@link #openTypedAssetFileDescriptor}
+ * for you with a MIME type of "*\/*". This allows such callers to benefit
+ * from any built-in data conversion that a provider implements.
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openAssetFile
* ContentProvider.openAssetFile}.
@@ -459,29 +496,97 @@ public abstract class ContentResolver {
new File(uri.getPath()), modeToMode(uri, mode));
return new AssetFileDescriptor(pfd, 0, -1);
} else {
- IContentProvider provider = acquireProvider(uri);
- if (provider == null) {
- throw new FileNotFoundException("No content provider: " + uri);
- }
- try {
- AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
- if(fd == null) {
- releaseProvider(provider);
- return null;
+ if ("r".equals(mode)) {
+ return openTypedAssetFileDescriptor(uri, "*/*", null);
+ } else {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
}
- ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
- fd.getParcelFileDescriptor(), provider);
- return new AssetFileDescriptor(pfd, fd.getStartOffset(),
- fd.getDeclaredLength());
- } catch (RemoteException e) {
- releaseProvider(provider);
- throw new FileNotFoundException("Dead content provider: " + uri);
- } catch (FileNotFoundException e) {
+ try {
+ AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+ if(fd == null) {
+ releaseProvider(provider);
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ provider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (provider != null) {
+ releaseProvider(provider);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Open a raw file descriptor to access (potentially type transformed)
+ * data from a "content:" URI. This interacts with the underlying
+ * {@link ContentProvider#openTypedAssetFile} method of the provider
+ * associated with the given URI, to retrieve retrieve any appropriate
+ * data stream for the data stored there.
+ *
+ * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
+ * with "content:" URIs, because content providers are the only facility
+ * with an associated MIME type to ensure that the returned data stream
+ * is of the desired type.
+ *
+ * <p>All text/* streams are encoded in UTF-8.
+ *
+ * @param uri The desired URI to open.
+ * @param mimeType The desired MIME type of the returned data. This can
+ * be a pattern such as *\/*, which will allow the content provider to
+ * select a type, though there is no way for you to determine what type
+ * it is returning.
+ * @param opts Additional provider-dependent options.
+ * @return Returns a new ParcelFileDescriptor from which you can read the
+ * data stream from the provider. Note that this may be a pipe, meaning
+ * you can't seek in it. The only seek you should do is if the
+ * AssetFileDescriptor contains an offset, to move to that offset before
+ * reading. You own this descriptor and are responsible for closing it when done.
+ * @throws FileNotFoundException Throws FileNotFoundException of no
+ * data of the desired type exists under the URI.
+ */
+ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
+ String mimeType, Bundle opts) throws FileNotFoundException {
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new FileNotFoundException("No content provider: " + uri);
+ }
+ try {
+ AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts);
+ if (fd == null) {
releaseProvider(provider);
- throw e;
- } catch (RuntimeException e) {
+ return null;
+ }
+ ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+ fd.getParcelFileDescriptor(), provider);
+
+ // Success! Don't release the provider when exiting, let
+ // ParcelFileDescriptorInner do that when it is closed.
+ provider = null;
+
+ return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+ fd.getDeclaredLength());
+ } catch (RemoteException e) {
+ throw new FileNotFoundException("Dead content provider: " + uri);
+ } catch (FileNotFoundException e) {
+ throw e;
+ } finally {
+ if (provider != null) {
releaseProvider(provider);
- throw e;
}
}
}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 67e7581e5ffd..8f122ce8f39d 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,7 @@ public interface IContentProvider extends IInterface {
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
+
/**
* @hide -- until interface has proven itself
*
@@ -71,6 +72,11 @@ public interface IContentProvider extends IInterface {
*/
public Bundle call(String method, String request, Bundle args) throws RemoteException;
+ // Data interchange.
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException;
+
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
@@ -84,4 +90,6 @@ public interface IContentProvider extends IInterface {
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
+ static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
+ static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2acc4a05792a..58174fbaa0de 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -985,6 +985,15 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_INSERT = "android.intent.action.INSERT";
/**
+ * Activity Action: Create a new item in the given container, initializing it
+ * from the current contents of the clipboard.
+ * <p>Input: {@link #getData} is URI of the directory (vnd.android.cursor.dir/*)
+ * in which to place the data.
+ * <p>Output: URI of the new data that was created.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PASTE = "android.intent.action.PASTE";
+ /**
* Activity Action: Delete the given data from its container.
* <p>Input: {@link #getData} is URI of data to be deleted.
* <p>Output: nothing.
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index a37e4e8cc3bf..ccb86050facd 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -129,8 +129,12 @@ public class AssetFileDescriptor implements Parcelable {
/**
* Checks whether this file descriptor is for a memory file.
*/
- private boolean isMemoryFile() throws IOException {
- return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ private boolean isMemoryFile() {
+ try {
+ return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ } catch (IOException e) {
+ return false;
+ }
}
/**
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 832ce84f52ef..aadacabe5e3d 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -175,6 +175,11 @@ public abstract class AsyncTask<Params, Progress, Result> {
FINISHED,
}
+ /** @hide Used to force static handler to be created. */
+ public static void init() {
+ sHandler.getLooper();
+ }
+
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 9d213b344dc8..d853f1375680 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -134,6 +134,25 @@ public class ParcelFileDescriptor implements Parcelable {
private static native FileDescriptor getFileDescriptorFromSocket(Socket socket);
/**
+ * Create two ParcelFileDescriptors structured as a data pipe. The first
+ * ParcelFileDescriptor in the returned array is the read side; the second
+ * is the write side.
+ */
+ public static ParcelFileDescriptor[] createPipe() throws IOException {
+ FileDescriptor[] fds = new FileDescriptor[2];
+ int res = createPipeNative(fds);
+ if (res == 0) {
+ ParcelFileDescriptor[] pfds = new ParcelFileDescriptor[2];
+ pfds[0] = new ParcelFileDescriptor(fds[0]);
+ pfds[1] = new ParcelFileDescriptor(fds[1]);
+ return pfds;
+ }
+ throw new IOException("Unable to create pipe: errno=" + -res);
+ }
+
+ private static native int createPipeNative(FileDescriptor[] outFds);
+
+ /**
* Retrieve the actual FileDescriptor associated with this object.
*
* @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 24f14dc8bdc7..f3f68f9616cb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -21,7 +21,9 @@ import com.android.internal.widget.EditableInputConnection;
import org.xmlpull.v1.XmlPullParserException;
+import android.content.ClippedData;
import android.content.Context;
+import android.content.ClipboardManager;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -34,6 +36,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -42,7 +45,6 @@ import android.os.Parcelable;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.text.BoringLayout;
-import android.text.ClipboardManager;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
@@ -7061,7 +7063,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
getSelectionStart() >= 0 &&
getSelectionEnd() >= 0 &&
((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
- hasText());
+ hasPrimaryClip());
}
/**
@@ -7270,7 +7272,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int min = Math.max(0, Math.min(selStart, selEnd));
int max = Math.max(0, Math.max(selStart, selEnd));
- ClipboardManager clip = (ClipboardManager)getContext()
+ ClipboardManager clipboard = (ClipboardManager)getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
switch (id) {
@@ -7278,8 +7280,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
- if (urls.length == 1) {
- clip.setText(urls[0].getURL());
+ if (urls.length >= 1) {
+ ClippedData clip = null;
+ for (int i=0; i<urls.length; i++) {
+ Uri uri = Uri.parse(urls[0].getURL());
+ ClippedData.Item item = new ClippedData.Item(uri);
+ if (clip == null) {
+ clip = new ClippedData(null, null, item);
+ } else {
+ clip.addItem(item);
+ }
+ }
+ clipboard.setPrimaryClip(clip);
}
return true;
@@ -7490,8 +7502,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- ClipboardManager clip = (ClipboardManager) getContext().
- getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipboardManager clipboard = (ClipboardManager) getContext().
+ getSystemService(Context.CLIPBOARD_SERVICE);
int min = 0;
int max = mText.length();
@@ -7506,23 +7518,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (item.getItemId()) {
case ID_PASTE:
- CharSequence paste = clip.getText();
-
- if (paste != null) {
- Selection.setSelection((Spannable) mText, max);
- ((Editable) mText).replace(min, max, paste);
+ ClippedData clip = clipboard.getPrimaryClip();
+ if (clip != null) {
+ boolean didfirst = false;
+ for (int i=0; i<clip.getItemCount(); i++) {
+ CharSequence paste = clip.getItem(i).coerceToText(getContext());
+ if (paste != null) {
+ if (!didfirst) {
+ Selection.setSelection((Spannable) mText, max);
+ ((Editable) mText).replace(min, max, paste);
+ } else {
+ ((Editable) mText).insert(getSelectionEnd(), "\n");
+ ((Editable) mText).insert(getSelectionEnd(), paste);
+ }
+ }
+ }
finishSelectionActionMode();
}
+
return true;
case ID_CUT:
- clip.setText(mTransformed.subSequence(min, max));
+ clipboard.setPrimaryClip(new ClippedData(null, null,
+ new ClippedData.Item(mTransformed.subSequence(min, max))));
((Editable) mText).delete(min, max);
finishSelectionActionMode();
return true;
case ID_COPY:
- clip.setText(mTransformed.subSequence(min, max));
+ clipboard.setPrimaryClip(new ClippedData(null, null,
+ new ClippedData.Item(mTransformed.subSequence(min, max))));
finishSelectionActionMode();
return true;
}
diff --git a/core/jni/android_os_ParcelFileDescriptor.cpp b/core/jni/android_os_ParcelFileDescriptor.cpp
index 848a57adea4d..eceef1c54620 100644
--- a/core/jni/android_os_ParcelFileDescriptor.cpp
+++ b/core/jni/android_os_ParcelFileDescriptor.cpp
@@ -66,6 +66,26 @@ static jobject android_os_ParcelFileDescriptor_getFileDescriptorFromSocket(JNIEn
return fileDescriptorClone;
}
+static int android_os_ParcelFileDescriptor_createPipeNative(JNIEnv* env,
+ jobject clazz, jobjectArray outFds)
+{
+ int fds[2];
+ if (pipe(fds) < 0) {
+ return -errno;
+ }
+
+ for (int i=0; i<2; i++) {
+ jobject fdObj = env->NewObject(gFileDescriptorOffsets.mClass,
+ gFileDescriptorOffsets.mConstructor);
+ if (fdObj != NULL) {
+ env->SetIntField(fdObj, gFileDescriptorOffsets.mDescriptor, fds[i]);
+ }
+ env->SetObjectArrayElement(outFds, i, fdObj);
+ }
+
+ return 0;
+}
+
static jint getFd(JNIEnv* env, jobject clazz)
{
jobject descriptor = env->GetObjectField(clazz, gParcelFileDescriptorOffsets.mFileDescriptor);
@@ -109,6 +129,8 @@ static jlong android_os_ParcelFileDescriptor_seekTo(JNIEnv* env,
static const JNINativeMethod gParcelFileDescriptorMethods[] = {
{"getFileDescriptorFromSocket", "(Ljava/net/Socket;)Ljava/io/FileDescriptor;",
(void*)android_os_ParcelFileDescriptor_getFileDescriptorFromSocket},
+ {"createPipeNative", "([Ljava/io/FileDescriptor;)I",
+ (void*)android_os_ParcelFileDescriptor_createPipeNative},
{"getStatSize", "()J",
(void*)android_os_ParcelFileDescriptor_getStatSize},
{"seekTo", "(J)J",
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 3fd71c8426d0..b63ff3dca8df 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -127,6 +127,16 @@ public class MockContentProvider extends ContentProvider {
throw new UnsupportedOperationException();
}
+ @SuppressWarnings("unused")
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+ return MockContentProvider.this.getStreamTypes(url, mimeTypeFilter);
+ }
+
+ @SuppressWarnings("unused")
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ return MockContentProvider.this.openTypedAssetFile(url, mimeType, opts);
+ }
}
private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
@@ -222,6 +232,14 @@ public class MockContentProvider extends ContentProvider {
throw new UnsupportedOperationException("unimplemented mock method call");
}
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) {
+ throw new UnsupportedOperationException("unimplemented mock method call");
+ }
+
/**
* Returns IContentProvider which calls back same methods in this class.
* By overriding this class, we avoid the mechanism hidden behind ContentProvider
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index 0be5bea276f3..183be415880c 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -32,6 +32,7 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import java.io.FileNotFoundException;
import java.util.ArrayList;
/**
@@ -102,4 +103,13 @@ public class MockIContentProvider implements IContentProvider {
public IBinder asBinder() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+
+ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
+ throws RemoteException, FileNotFoundException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
}