diff options
| -rw-r--r-- | api/current.xml | 223 | ||||
| -rw-r--r-- | core/java/android/content/ClipboardManager.java | 12 | ||||
| -rw-r--r-- | core/java/android/content/ClippedData.java | 227 | ||||
| -rw-r--r-- | core/java/android/content/ContentProvider.java | 177 | ||||
| -rw-r--r-- | core/java/android/content/ContentProviderClient.java | 43 | ||||
| -rw-r--r-- | core/java/android/content/ContentProviderNative.java | 77 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 153 | ||||
| -rw-r--r-- | core/java/android/content/IContentProvider.java | 8 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 9 | ||||
| -rw-r--r-- | core/java/android/content/res/AssetFileDescriptor.java | 8 | ||||
| -rw-r--r-- | core/java/android/os/AsyncTask.java | 5 | ||||
| -rw-r--r-- | core/java/android/os/ParcelFileDescriptor.java | 19 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 53 | ||||
| -rw-r--r-- | core/jni/android_os_ParcelFileDescriptor.cpp | 22 | ||||
| -rw-r--r-- | test-runner/src/android/test/mock/MockContentProvider.java | 18 | ||||
| -rw-r--r-- | test-runner/src/android/test/mock/MockIContentProvider.java | 10 |
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<T>"> +</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=""android.intent.action.PASTE"" + 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"); + } } |