blob: ed3d4557061e915606a827aa68c92c400b54aa0f [file] [log] [blame]
Dianne Hackborn9f531192010-08-04 17:48:03 -07001/**
2 * Copyright (c) 2010, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content;
18
Nicolas Prevotd85fc722014-04-16 19:52:08 +010019import static android.content.ContentProvider.maybeAddUserId;
Vladislav Kaznacheeve1f5e132017-01-27 16:56:40 -080020import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
21import static android.content.ContentResolver.SCHEME_CONTENT;
22import static android.content.ContentResolver.SCHEME_FILE;
Vladislav Kaznacheevc14df8e2016-01-22 11:49:13 -080023
Mathew Inwood1c77a112018-08-14 14:06:26 +010024import android.annotation.UnsupportedAppUsage;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070025import android.content.res.AssetFileDescriptor;
Dianne Hackborn9f531192010-08-04 17:48:03 -070026import android.graphics.Bitmap;
27import android.net.Uri;
Mathew Inwood45d2c252018-09-14 12:35:36 +010028import android.os.Build;
Dianne Hackborn9f531192010-08-04 17:48:03 -070029import android.os.Parcel;
30import android.os.Parcelable;
Jeff Sharkeya14acd22013-04-02 18:27:45 -070031import android.os.StrictMode;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -070032import android.text.Html;
33import android.text.Spannable;
34import android.text.SpannableStringBuilder;
35import android.text.Spanned;
Dianne Hackborn9f531192010-08-04 17:48:03 -070036import android.text.TextUtils;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -070037import android.text.style.URLSpan;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070038import android.util.Log;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080039import android.util.proto.ProtoOutputStream;
Dianne Hackborn9f531192010-08-04 17:48:03 -070040
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -080041import com.android.internal.util.ArrayUtils;
42
Kweku Adams85f2fbc2017-12-18 12:04:12 -080043import libcore.io.IoUtils;
44
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070045import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.IOException;
48import java.io.InputStreamReader;
Dianne Hackborn9f531192010-08-04 17:48:03 -070049import java.util.ArrayList;
Vladislav Kaznacheev9149d2b2015-12-15 12:16:28 -080050import java.util.List;
Dianne Hackborn9f531192010-08-04 17:48:03 -070051
52/**
53 * Representation of a clipped data on the clipboard.
54 *
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -070055 * <p>ClipData is a complex type containing one or more Item instances,
Dianne Hackborn9f531192010-08-04 17:48:03 -070056 * each of which can hold one or more representations of an item of data.
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -070057 * For display to the user, it also has a label.</p>
Dianne Hackborn9f531192010-08-04 17:48:03 -070058 *
Dianne Hackbornf834dfa2010-10-26 12:43:57 -070059 * <p>A ClipData contains a {@link ClipDescription}, which describes
60 * important meta-data about the clip. In particular, its
61 * {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}
Dianne Hackborn1040dc42010-08-26 22:11:06 -070062 * must return correct MIME type(s) describing the data in the clip. For help
63 * in correctly constructing a clip with the correct MIME type, use
Dianne Hackborn327fbd22011-01-17 14:38:50 -080064 * {@link #newPlainText(CharSequence, CharSequence)},
65 * {@link #newUri(ContentResolver, CharSequence, Uri)}, and
66 * {@link #newIntent(CharSequence, Intent)}.
Dianne Hackborn1040dc42010-08-26 22:11:06 -070067 *
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070068 * <p>Each Item instance can be one of three main classes of data: a simple
69 * CharSequence of text, a single Intent object, or a Uri. See {@link Item}
70 * for more details.
Dianne Hackborn9f531192010-08-04 17:48:03 -070071 *
Joe Fernandez3aef8e1d2011-12-20 10:38:34 -080072 * <div class="special reference">
73 * <h3>Developer Guides</h3>
74 * <p>For more information about using the clipboard framework, read the
75 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
76 * developer guide.</p>
77 * </div>
78 *
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070079 * <a name="ImplementingPaste"></a>
80 * <h3>Implementing Paste or Drop</h3>
81 *
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -070082 * <p>To implement a paste or drop of a ClipData object into an application,
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070083 * the application must correctly interpret the data for its use. If the {@link Item}
84 * it contains is simple text or an Intent, there is little to be done: text
85 * can only be interpreted as text, and an Intent will typically be used for
86 * creating shortcuts (such as placing icons on the home screen) or other
87 * actions.
88 *
89 * <p>If all you want is the textual representation of the clipped data, you
90 * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
Dianne Hackborn1040dc42010-08-26 22:11:06 -070091 * In this case there is generally no need to worry about the MIME types
Dianne Hackbornf834dfa2010-10-26 12:43:57 -070092 * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)},
Newton Allen4465d1a2013-11-26 10:25:38 -080093 * since any clip item can always be converted to a string.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070094 *
95 * <p>More complicated exchanges will be done through URIs, in particular
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -070096 * "content:" URIs. A content URI allows the recipient of a ClipData item
Dianne Hackborn23fdaf62010-08-06 12:16:55 -070097 * to interact closely with the ContentProvider holding the data in order to
Dianne Hackborn1040dc42010-08-26 22:11:06 -070098 * negotiate the transfer of that data. The clip must also be filled in with
Dianne Hackborn327fbd22011-01-17 14:38:50 -080099 * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Uri)}
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700100 * will take care of correctly doing this.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700101 *
102 * <p>For example, here is the paste function of a simple NotePad application.
103 * When retrieving the data from the clipboard, it can do either two things:
104 * if the clipboard contains a URI reference to an existing note, it copies
105 * the entire structure of the note into a new note; otherwise, it simply
106 * coerces the clip into text and uses that as the new note's contents.
107 *
108 * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
109 * paste}
110 *
111 * <p>In many cases an application can paste various types of streams of data. For
112 * example, an e-mail application may want to allow the user to paste an image
113 * or other binary data as an attachment. This is accomplished through the
114 * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
115 * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
116 * methods. These allow a client to discover the type(s) of data that a particular
117 * content URI can make available as a stream and retrieve the stream of data.
118 *
119 * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
120 * itself uses this to try to retrieve a URI clip as a stream of text:
121 *
Dianne Hackbornf6d952b2010-08-27 15:41:13 -0700122 * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText}
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700123 *
124 * <a name="ImplementingCopy"></a>
125 * <h3>Implementing Copy or Drag</h3>
126 *
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -0700127 * <p>To be the source of a clip, the application must construct a ClipData
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700128 * object that any recipient can interpret best for their context. If the clip
129 * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
130 * containing the appropriate data type can be constructed and used.
131 *
132 * <p>More complicated data types require the implementation of support in
133 * a ContentProvider for describing and generating the data for the recipient.
134 * A common scenario is one where an application places on the clipboard the
135 * content: URI of an object that the user has copied, with the data at that
136 * URI consisting of a complicated structure that only other applications with
137 * direct knowledge of the structure can use.
138 *
139 * <p>For applications that do not have intrinsic knowledge of the data structure,
140 * the content provider holding it can make the data available as an arbitrary
141 * number of types of data streams. This is done by implementing the
142 * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
143 * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
144 * methods.
145 *
146 * <p>Going back to our simple NotePad application, this is the implementation
147 * it may have to convert a single note URI (consisting of a title and the note
148 * text) into a stream of plain text data.
149 *
150 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
151 * stream}
152 *
153 * <p>The copy operation in our NotePad application is now just a simple matter
154 * of making a clip containing the URI of the note being copied:
155 *
156 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
157 * copy}
158 *
159 * <p>Note if a paste operation needs this clip as text (for example to paste
160 * into an editor), then {@link Item#coerceToText(Context)} will ask the content
161 * provider for the clip URI as text and successfully paste the entire note.
Dianne Hackborn9f531192010-08-04 17:48:03 -0700162 */
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700163public class ClipData implements Parcelable {
164 static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
165 ClipDescription.MIMETYPE_TEXT_PLAIN };
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700166 static final String[] MIMETYPES_TEXT_HTML = new String[] {
167 ClipDescription.MIMETYPE_TEXT_HTML };
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700168 static final String[] MIMETYPES_TEXT_URILIST = new String[] {
169 ClipDescription.MIMETYPE_TEXT_URILIST };
170 static final String[] MIMETYPES_TEXT_INTENT = new String[] {
171 ClipDescription.MIMETYPE_TEXT_INTENT };
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700172
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700173 final ClipDescription mClipDescription;
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -0700174
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700175 final Bitmap mIcon;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700176
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800177 final ArrayList<Item> mItems;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700178
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700179 /**
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -0700180 * Description of a single item in a ClipData.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700181 *
182 * <p>The types than an individual item can currently contain are:</p>
183 *
184 * <ul>
185 * <li> Text: a basic string of text. This is actually a CharSequence,
186 * so it can be formatted text supported by corresponding Android built-in
187 * style spans. (Custom application spans are not supported and will be
188 * stripped when transporting through the clipboard.)
189 * <li> Intent: an arbitrary Intent object. A typical use is the shortcut
190 * to create when pasting a clipped item on to the home screen.
191 * <li> Uri: a URI reference. This may be any URI (such as an http: URI
192 * representing a bookmark), however it is often a content: URI. Using
193 * content provider references as clips like this allows an application to
194 * share complex or large clips through the standard content provider
195 * facilities.
196 * </ul>
197 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700198 public static class Item {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700199 final CharSequence mText;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700200 final String mHtmlText;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700201 final Intent mIntent;
Mathew Inwood45d2c252018-09-14 12:35:36 +0100202 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100203 Uri mUri;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700204
Nicolas Prevot58926242016-07-14 19:11:10 +0100205 /** @hide */
206 public Item(Item other) {
207 mText = other.mText;
208 mHtmlText = other.mHtmlText;
209 mIntent = other.mIntent;
210 mUri = other.mUri;
211 }
212
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700213 /**
214 * Create an Item consisting of a single block of (possibly styled) text.
215 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700216 public Item(CharSequence text) {
217 mText = text;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700218 mHtmlText = null;
219 mIntent = null;
220 mUri = null;
221 }
222
223 /**
224 * Create an Item consisting of a single block of (possibly styled) text,
225 * with an alternative HTML formatted representation. You <em>must</em>
226 * supply a plain text representation in addition to HTML text; coercion
kopriva219f7dc2018-10-09 13:42:28 -0700227 * will not be done from HTML formatted text into plain text.
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700228 */
229 public Item(CharSequence text, String htmlText) {
230 mText = text;
231 mHtmlText = htmlText;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700232 mIntent = null;
233 mUri = null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700234 }
235
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700236 /**
237 * Create an Item consisting of an arbitrary Intent.
238 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700239 public Item(Intent intent) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700240 mText = null;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700241 mHtmlText = null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700242 mIntent = intent;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700243 mUri = null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700244 }
245
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700246 /**
247 * Create an Item consisting of an arbitrary URI.
248 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700249 public Item(Uri uri) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700250 mText = null;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700251 mHtmlText = null;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700252 mIntent = null;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700253 mUri = uri;
254 }
255
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700256 /**
257 * Create a complex Item, containing multiple representations of
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700258 * text, Intent, and/or URI.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700259 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700260 public Item(CharSequence text, Intent intent, Uri uri) {
261 mText = text;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700262 mHtmlText = null;
263 mIntent = intent;
264 mUri = uri;
265 }
266
267 /**
268 * Create a complex Item, containing multiple representations of
269 * text, HTML text, Intent, and/or URI. If providing HTML text, you
270 * <em>must</em> supply a plain text representation as well; coercion
kopriva219f7dc2018-10-09 13:42:28 -0700271 * will not be done from HTML formatted text into plain text.
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700272 */
273 public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
274 if (htmlText != null && text == null) {
275 throw new IllegalArgumentException(
276 "Plain text must be supplied if HTML text is supplied");
277 }
278 mText = text;
279 mHtmlText = htmlText;
Dianne Hackborn9f531192010-08-04 17:48:03 -0700280 mIntent = intent;
281 mUri = uri;
282 }
283
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700284 /**
285 * Retrieve the raw text contained in this Item.
286 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700287 public CharSequence getText() {
288 return mText;
289 }
290
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700291 /**
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700292 * Retrieve the raw HTML text contained in this Item.
293 */
294 public String getHtmlText() {
295 return mHtmlText;
296 }
297
298 /**
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700299 * Retrieve the raw Intent contained in this Item.
300 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700301 public Intent getIntent() {
302 return mIntent;
303 }
304
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700305 /**
306 * Retrieve the raw URI contained in this Item.
307 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700308 public Uri getUri() {
309 return mUri;
310 }
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700311
312 /**
313 * Turn this item into text, regardless of the type of data it
314 * actually contains.
315 *
316 * <p>The algorithm for deciding what text to return is:
317 * <ul>
318 * <li> If {@link #getText} is non-null, return that.
319 * <li> If {@link #getUri} is non-null, try to retrieve its data
320 * as a text stream from its content provider. If this succeeds, copy
321 * the text into a String and return it. If it is not a content: URI or
322 * the content provider does not supply a text representation, return
323 * the raw URI as a string.
324 * <li> If {@link #getIntent} is non-null, convert that to an intent:
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700325 * URI and return it.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700326 * <li> Otherwise, return an empty string.
327 * </ul>
328 *
329 * @param context The caller's Context, from which its ContentResolver
330 * and other things can be retrieved.
331 * @return Returns the item's textual representation.
332 */
333//BEGIN_INCLUDE(coerceToText)
334 public CharSequence coerceToText(Context context) {
335 // If this Item has an explicit textual value, simply return that.
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700336 CharSequence text = getText();
337 if (text != null) {
338 return text;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700339 }
340
341 // If this Item has a URI value, try using that.
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700342 Uri uri = getUri();
343 if (uri != null) {
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700344 // First see if the URI can be opened as a plain text stream
345 // (of any sub-type). If so, this is the best textual
346 // representation for it.
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900347 final ContentResolver resolver = context.getContentResolver();
348 AssetFileDescriptor descr = null;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700349 FileInputStream stream = null;
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900350 InputStreamReader reader = null;
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700351 try {
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900352 try {
353 // Ask for a stream of the desired type.
354 descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
355 } catch (SecurityException e) {
356 Log.w("ClipData", "Failure opening stream", e);
357 } catch (FileNotFoundException|RuntimeException e) {
358 // Unable to open content URI as text... not really an
359 // error, just something to ignore.
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700360 }
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900361 if (descr != null) {
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700362 try {
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900363 stream = descr.createInputStream();
364 reader = new InputStreamReader(stream, "UTF-8");
365
366 // Got it... copy the stream into a local string and return it.
367 final StringBuilder builder = new StringBuilder(128);
368 char[] buffer = new char[8192];
369 int len;
370 while ((len=reader.read(buffer)) > 0) {
371 builder.append(buffer, 0, len);
372 }
373 return builder.toString();
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700374 } catch (IOException e) {
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900375 // Something bad has happened.
376 Log.w("ClipData", "Failure loading text", e);
377 return e.toString();
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700378 }
379 }
Daichi Hirono3d9f3fc2017-04-12 14:23:06 +0900380 } finally {
381 IoUtils.closeQuietly(descr);
382 IoUtils.closeQuietly(stream);
383 IoUtils.closeQuietly(reader);
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700384 }
385
Vladislav Kaznacheeve1f5e132017-01-27 16:56:40 -0800386 // If we couldn't open the URI as a stream, use the URI itself as a textual
387 // representation (but not for "content", "android.resource" or "file" schemes).
388 final String scheme = uri.getScheme();
389 if (SCHEME_CONTENT.equals(scheme)
390 || SCHEME_ANDROID_RESOURCE.equals(scheme)
391 || SCHEME_FILE.equals(scheme)) {
392 return "";
393 }
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700394 return uri.toString();
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700395 }
396
397 // Finally, if all we have is an Intent, then we can just turn that
398 // into text. Not the most user-friendly thing, but it's something.
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700399 Intent intent = getIntent();
400 if (intent != null) {
401 return intent.toUri(Intent.URI_INTENT_SCHEME);
Dianne Hackborn23fdaf62010-08-06 12:16:55 -0700402 }
403
404 // Shouldn't get here, but just in case...
405 return "";
406 }
407//END_INCLUDE(coerceToText)
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800408
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700409 /**
410 * Like {@link #coerceToHtmlText(Context)}, but any text that would
411 * be returned as HTML formatting will be returned as text with
412 * style spans.
413 * @param context The caller's Context, from which its ContentResolver
414 * and other things can be retrieved.
415 * @return Returns the item's textual representation.
416 */
417 public CharSequence coerceToStyledText(Context context) {
418 CharSequence text = getText();
419 if (text instanceof Spanned) {
420 return text;
421 }
422 String htmlText = getHtmlText();
423 if (htmlText != null) {
424 try {
425 CharSequence newText = Html.fromHtml(htmlText);
426 if (newText != null) {
427 return newText;
428 }
429 } catch (RuntimeException e) {
430 // If anything bad happens, we'll fall back on the plain text.
431 }
432 }
433
434 if (text != null) {
435 return text;
436 }
437 return coerceToHtmlOrStyledText(context, true);
438 }
439
440 /**
441 * Turn this item into HTML text, regardless of the type of data it
442 * actually contains.
443 *
444 * <p>The algorithm for deciding what text to return is:
445 * <ul>
446 * <li> If {@link #getHtmlText} is non-null, return that.
447 * <li> If {@link #getText} is non-null, return that, converting to
448 * valid HTML text. If this text contains style spans,
449 * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to
450 * convert them to HTML formatting.
451 * <li> If {@link #getUri} is non-null, try to retrieve its data
452 * as a text stream from its content provider. If the provider can
453 * supply text/html data, that will be preferred and returned as-is.
454 * Otherwise, any text/* data will be returned and escaped to HTML.
455 * If it is not a content: URI or the content provider does not supply
456 * a text representation, HTML text containing a link to the URI
457 * will be returned.
458 * <li> If {@link #getIntent} is non-null, convert that to an intent:
459 * URI and return as an HTML link.
460 * <li> Otherwise, return an empty string.
461 * </ul>
462 *
463 * @param context The caller's Context, from which its ContentResolver
464 * and other things can be retrieved.
465 * @return Returns the item's representation as HTML text.
466 */
467 public String coerceToHtmlText(Context context) {
468 // If the item has an explicit HTML value, simply return that.
469 String htmlText = getHtmlText();
470 if (htmlText != null) {
471 return htmlText;
472 }
473
474 // If this Item has a plain text value, return it as HTML.
475 CharSequence text = getText();
476 if (text != null) {
477 if (text instanceof Spanned) {
478 return Html.toHtml((Spanned)text);
479 }
480 return Html.escapeHtml(text);
481 }
482
483 text = coerceToHtmlOrStyledText(context, false);
484 return text != null ? text.toString() : null;
485 }
486
487 private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) {
488 // If this Item has a URI value, try using that.
489 if (mUri != null) {
490
491 // Check to see what data representations the content
492 // provider supports. We would like HTML text, but if that
493 // is not possible we'll live with plan text.
Vladislav Kaznacheevc14df8e2016-01-22 11:49:13 -0800494 String[] types = null;
495 try {
496 types = context.getContentResolver().getStreamTypes(mUri, "text/*");
497 } catch (SecurityException e) {
498 // No read permission for mUri, assume empty stream types list.
499 }
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700500 boolean hasHtml = false;
501 boolean hasText = false;
502 if (types != null) {
503 for (String type : types) {
504 if ("text/html".equals(type)) {
505 hasHtml = true;
506 } else if (type.startsWith("text/")) {
507 hasText = true;
508 }
509 }
510 }
511
512 // If the provider can serve data we can use, open and load it.
513 if (hasHtml || hasText) {
514 FileInputStream stream = null;
515 try {
516 // Ask for a stream of the desired type.
517 AssetFileDescriptor descr = context.getContentResolver()
518 .openTypedAssetFileDescriptor(mUri,
519 hasHtml ? "text/html" : "text/plain", null);
520 stream = descr.createInputStream();
521 InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
522
523 // Got it... copy the stream into a local string and return it.
524 StringBuilder builder = new StringBuilder(128);
525 char[] buffer = new char[8192];
526 int len;
527 while ((len=reader.read(buffer)) > 0) {
528 builder.append(buffer, 0, len);
529 }
530 String text = builder.toString();
531 if (hasHtml) {
532 if (styled) {
533 // We loaded HTML formatted text and the caller
534 // want styled text, convert it.
535 try {
536 CharSequence newText = Html.fromHtml(text);
537 return newText != null ? newText : text;
538 } catch (RuntimeException e) {
539 return text;
540 }
541 } else {
542 // We loaded HTML formatted text and that is what
543 // the caller wants, just return it.
544 return text.toString();
545 }
546 }
547 if (styled) {
548 // We loaded plain text and the caller wants styled
549 // text, that is all we have so return it.
550 return text;
551 } else {
552 // We loaded plain text and the caller wants HTML
553 // text, escape it for HTML.
554 return Html.escapeHtml(text);
555 }
556
Vladislav Kaznacheevb143ff62017-01-27 16:39:13 -0800557 } catch (SecurityException e) {
558 Log.w("ClipData", "Failure opening stream", e);
559
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700560 } catch (FileNotFoundException e) {
561 // Unable to open content URI as text... not really an
562 // error, just something to ignore.
563
564 } catch (IOException e) {
565 // Something bad has happened.
Vladislav Kaznacheev7e4669c2016-05-02 17:04:08 -0700566 Log.w("ClipData", "Failure loading text", e);
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700567 return Html.escapeHtml(e.toString());
568
569 } finally {
570 if (stream != null) {
571 try {
572 stream.close();
573 } catch (IOException e) {
574 }
575 }
576 }
577 }
578
Vladislav Kaznacheeve1f5e132017-01-27 16:56:40 -0800579 // If we couldn't open the URI as a stream, use the URI itself as a textual
580 // representation (but not for "content", "android.resource" or "file" schemes).
581 final String scheme = mUri.getScheme();
582 if (SCHEME_CONTENT.equals(scheme)
583 || SCHEME_ANDROID_RESOURCE.equals(scheme)
584 || SCHEME_FILE.equals(scheme)) {
585 return "";
586 }
587
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700588 if (styled) {
589 return uriToStyledText(mUri.toString());
590 } else {
591 return uriToHtml(mUri.toString());
592 }
593 }
594
595 // Finally, if all we have is an Intent, then we can just turn that
596 // into text. Not the most user-friendly thing, but it's something.
597 if (mIntent != null) {
598 if (styled) {
599 return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME));
600 } else {
601 return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME));
602 }
603 }
604
605 // Shouldn't get here, but just in case...
606 return "";
607 }
608
609 private String uriToHtml(String uri) {
610 StringBuilder builder = new StringBuilder(256);
611 builder.append("<a href=\"");
Nick Kralevichc92db392012-07-27 13:22:20 -0700612 builder.append(Html.escapeHtml(uri));
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700613 builder.append("\">");
614 builder.append(Html.escapeHtml(uri));
615 builder.append("</a>");
616 return builder.toString();
617 }
618
619 private CharSequence uriToStyledText(String uri) {
620 SpannableStringBuilder builder = new SpannableStringBuilder();
621 builder.append(uri);
622 builder.setSpan(new URLSpan(uri), 0, builder.length(),
623 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
624 return builder;
625 }
626
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800627 @Override
628 public String toString() {
629 StringBuilder b = new StringBuilder(128);
630
631 b.append("ClipData.Item { ");
632 toShortString(b);
633 b.append(" }");
634
635 return b.toString();
636 }
637
638 /** @hide */
639 public void toShortString(StringBuilder b) {
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700640 if (mHtmlText != null) {
641 b.append("H:");
642 b.append(mHtmlText);
643 } else if (mText != null) {
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800644 b.append("T:");
645 b.append(mText);
646 } else if (mUri != null) {
647 b.append("U:");
648 b.append(mUri);
649 } else if (mIntent != null) {
650 b.append("I:");
651 mIntent.toShortString(b, true, true, true, true);
652 } else {
653 b.append("NULL");
654 }
655 }
Dianne Hackbornae498722015-08-14 13:29:47 -0700656
657 /** @hide */
658 public void toShortSummaryString(StringBuilder b) {
659 if (mHtmlText != null) {
660 b.append("HTML");
661 } else if (mText != null) {
662 b.append("TEXT");
663 } else if (mUri != null) {
664 b.append("U:");
665 b.append(mUri);
666 } else if (mIntent != null) {
667 b.append("I:");
668 mIntent.toShortString(b, true, true, true, true);
669 } else {
670 b.append("NULL");
671 }
672 }
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800673
674 /** @hide */
675 public void writeToProto(ProtoOutputStream proto, long fieldId) {
676 final long token = proto.start(fieldId);
677
678 if (mHtmlText != null) {
679 proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText);
680 } else if (mText != null) {
681 proto.write(ClipDataProto.Item.TEXT, mText.toString());
682 } else if (mUri != null) {
683 proto.write(ClipDataProto.Item.URI, mUri.toString());
684 } else if (mIntent != null) {
685 mIntent.writeToProto(proto, ClipDataProto.Item.INTENT, true, true, true, true);
686 } else {
687 proto.write(ClipDataProto.Item.NOTHING, true);
688 }
689
690 proto.end(token);
691 }
Dianne Hackborn9f531192010-08-04 17:48:03 -0700692 }
693
694 /**
695 * Create a new clip.
696 *
697 * @param label Label to show to the user describing this clip.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700698 * @param mimeTypes An array of MIME types this data is available as.
Dianne Hackborn9f531192010-08-04 17:48:03 -0700699 * @param item The contents of the first item in the clip.
700 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800701 public ClipData(CharSequence label, String[] mimeTypes, Item item) {
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700702 mClipDescription = new ClipDescription(label, mimeTypes);
Dianne Hackborn9f531192010-08-04 17:48:03 -0700703 if (item == null) {
704 throw new NullPointerException("item is null");
705 }
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800706 mIcon = null;
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800707 mItems = new ArrayList<Item>();
Dianne Hackborn9f531192010-08-04 17:48:03 -0700708 mItems.add(item);
709 }
710
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700711 /**
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700712 * Create a new clip.
713 *
714 * @param description The ClipDescription describing the clip contents.
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700715 * @param item The contents of the first item in the clip.
716 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800717 public ClipData(ClipDescription description, Item item) {
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700718 mClipDescription = description;
719 if (item == null) {
720 throw new NullPointerException("item is null");
721 }
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800722 mIcon = null;
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800723 mItems = new ArrayList<Item>();
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700724 mItems.add(item);
725 }
726
727 /**
Steve McKay57eaaf62016-06-13 12:53:31 -0700728 * Create a new clip.
729 *
730 * @param description The ClipDescription describing the clip contents.
731 * @param items The items in the clip. Not that a defensive copy of this
732 * list is not made, so caution should be taken to ensure the
733 * list is not available for further modification.
734 * @hide
735 */
736 public ClipData(ClipDescription description, ArrayList<Item> items) {
737 mClipDescription = description;
738 if (items == null) {
739 throw new NullPointerException("item is null");
740 }
741 mIcon = null;
742 mItems = items;
743 }
744
745 /**
Dianne Hackborn21c241e2012-03-08 13:57:23 -0800746 * Create a new clip that is a copy of another clip. This does a deep-copy
747 * of all items in the clip.
748 *
749 * @param other The existing ClipData that is to be copied.
750 */
751 public ClipData(ClipData other) {
752 mClipDescription = other.mClipDescription;
753 mIcon = other.mIcon;
754 mItems = new ArrayList<Item>(other.mItems);
755 }
756
757 /**
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700758 * Create a new ClipData holding data of the type
759 * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700760 *
761 * @param label User-visible label for the clip data.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700762 * @param text The actual text in the clip.
763 * @return Returns a new ClipData containing the specified data.
764 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800765 static public ClipData newPlainText(CharSequence label, CharSequence text) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700766 Item item = new Item(text);
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800767 return new ClipData(label, MIMETYPES_TEXT_PLAIN, item);
768 }
769
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700770 /**
Dianne Hackbornacb69bb2012-04-13 15:36:06 -0700771 * Create a new ClipData holding data of the type
772 * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
773 *
774 * @param label User-visible label for the clip data.
775 * @param text The text of clip as plain text, for receivers that don't
776 * handle HTML. This is required.
777 * @param htmlText The actual HTML text in the clip.
778 * @return Returns a new ClipData containing the specified data.
779 */
780 static public ClipData newHtmlText(CharSequence label, CharSequence text,
781 String htmlText) {
782 Item item = new Item(text, htmlText);
783 return new ClipData(label, MIMETYPES_TEXT_HTML, item);
784 }
785
786 /**
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700787 * Create a new ClipData holding an Intent with MIME type
788 * {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700789 *
790 * @param label User-visible label for the clip data.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700791 * @param intent The actual Intent in the clip.
792 * @return Returns a new ClipData containing the specified data.
793 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800794 static public ClipData newIntent(CharSequence label, Intent intent) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700795 Item item = new Item(intent);
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800796 return new ClipData(label, MIMETYPES_TEXT_INTENT, item);
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700797 }
798
799 /**
800 * Create a new ClipData holding a URI. If the URI is a content: URI,
801 * this will query the content provider for the MIME type of its data and
802 * use that as the MIME type. Otherwise, it will use the MIME type
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700803 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700804 *
805 * @param resolver ContentResolver used to get information about the URI.
806 * @param label User-visible label for the clip data.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700807 * @param uri The URI in the clip.
808 * @return Returns a new ClipData containing the specified data.
809 */
810 static public ClipData newUri(ContentResolver resolver, CharSequence label,
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800811 Uri uri) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700812 Item item = new Item(uri);
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800813 String[] mimeTypes = getMimeTypes(resolver, uri);
814 return new ClipData(label, mimeTypes, item);
815 }
816
817 /**
818 * Finds all applicable MIME types for a given URI.
819 *
820 * @param resolver ContentResolver used to get information about the URI.
821 * @param uri The URI.
822 * @return Returns an array of MIME types.
823 */
824 private static String[] getMimeTypes(ContentResolver resolver, Uri uri) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700825 String[] mimeTypes = null;
Vladislav Kaznacheeve1f5e132017-01-27 16:56:40 -0800826 if (SCHEME_CONTENT.equals(uri.getScheme())) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700827 String realType = resolver.getType(uri);
828 mimeTypes = resolver.getStreamTypes(uri, "*/*");
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700829 if (realType != null) {
830 if (mimeTypes == null) {
831 mimeTypes = new String[] { realType };
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800832 } else if (!ArrayUtils.contains(mimeTypes, realType)) {
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700833 String[] tmp = new String[mimeTypes.length + 1];
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700834 tmp[0] = realType;
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700835 System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length);
836 mimeTypes = tmp;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700837 }
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700838 }
839 }
840 if (mimeTypes == null) {
841 mimeTypes = MIMETYPES_TEXT_URILIST;
842 }
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800843 return mimeTypes;
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700844 }
845
846 /**
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700847 * Create a new ClipData holding an URI with MIME type
848 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800849 * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700850 * is inferred about the URI -- if it is a content: URI holding a bitmap,
851 * the reported type will still be uri-list. Use this with care!
852 *
853 * @param label User-visible label for the clip data.
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700854 * @param uri The URI in the clip.
855 * @return Returns a new ClipData containing the specified data.
856 */
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800857 static public ClipData newRawUri(CharSequence label, Uri uri) {
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700858 Item item = new Item(uri);
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800859 return new ClipData(label, MIMETYPES_TEXT_URILIST, item);
Dianne Hackborn1040dc42010-08-26 22:11:06 -0700860 }
861
Dianne Hackbornf834dfa2010-10-26 12:43:57 -0700862 /**
863 * Return the {@link ClipDescription} associated with this data, describing
864 * what it contains.
865 */
866 public ClipDescription getDescription() {
867 return mClipDescription;
868 }
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700869
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800870 /**
871 * Add a new Item to the overall ClipData container.
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700872 * <p> This method will <em>not</em> update the list of available MIME types in the
873 * {@link ClipDescription}. It should be used only when adding items which do not add new
Vladislav Kaznacheevc35c02f2017-04-20 08:38:21 -0700874 * MIME types to this clip. If this is not the case, use {@link #addItem(ContentResolver, Item)}
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800875 * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types.
Vladislav Kaznacheevf67661c2016-05-03 15:02:33 -0700876 * @param item Item to be added.
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800877 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700878 public void addItem(Item item) {
879 if (item == null) {
880 throw new NullPointerException("item is null");
881 }
882 mItems.add(item);
883 }
884
Vladislav Kaznacheevc35c02f2017-04-20 08:38:21 -0700885 /** @removed use #addItem(ContentResolver, Item) instead */
886 @Deprecated
887 public void addItem(Item item, ContentResolver resolver) {
888 addItem(resolver, item);
889 }
890
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800891 /**
892 * Add a new Item to the overall ClipData container.
893 * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types
894 * in the {@link ClipDescription}.
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800895 * @param resolver ContentResolver used to get information about the URI possibly contained in
896 * the item.
Vladislav Kaznacheevc35c02f2017-04-20 08:38:21 -0700897 * @param item Item to be added.
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800898 */
Vladislav Kaznacheevc35c02f2017-04-20 08:38:21 -0700899 public void addItem(ContentResolver resolver, Item item) {
Vladislav Kaznacheev8e7b9402016-11-23 15:31:13 -0800900 addItem(item);
901
902 if (item.getHtmlText() != null) {
903 mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML);
904 } else if (item.getText() != null) {
905 mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN);
906 }
907
908 if (item.getIntent() != null) {
909 mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT);
910 }
911
912 if (item.getUri() != null) {
913 mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri()));
914 }
915 }
916
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800917 /** @hide */
Mathew Inwood1c77a112018-08-14 14:06:26 +0100918 @UnsupportedAppUsage
Dianne Hackborn9f531192010-08-04 17:48:03 -0700919 public Bitmap getIcon() {
920 return mIcon;
921 }
922
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800923 /**
924 * Return the number of items in the clip data.
925 */
Dianne Hackborn9f531192010-08-04 17:48:03 -0700926 public int getItemCount() {
927 return mItems.size();
928 }
929
Dianne Hackborn327fbd22011-01-17 14:38:50 -0800930 /**
931 * Return a single item inside of the clip data. The index can range
932 * from 0 to {@link #getItemCount()}-1.
933 */
934 public Item getItemAt(int index) {
Dianne Hackborn9f531192010-08-04 17:48:03 -0700935 return mItems.get(index);
936 }
937
Nicolas Prevot58926242016-07-14 19:11:10 +0100938 /** @hide */
939 public void setItemAt(int index, Item item) {
940 mItems.set(index, item);
941 }
942
Jeff Sharkeya14acd22013-04-02 18:27:45 -0700943 /**
944 * Prepare this {@link ClipData} to leave an app process.
945 *
946 * @hide
947 */
Jeff Sharkey344744b2016-01-28 19:03:30 -0700948 public void prepareToLeaveProcess(boolean leavingPackage) {
Jeff Sharkeyf361f2f2016-12-21 10:48:42 -0700949 // Assume that callers are going to be granting permissions
950 prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
Jeff Sharkeyfb833f32016-12-01 14:59:59 -0700951 }
952
953 /**
954 * Prepare this {@link ClipData} to leave an app process.
955 *
956 * @hide
957 */
958 public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
Jeff Sharkeya14acd22013-04-02 18:27:45 -0700959 final int size = mItems.size();
960 for (int i = 0; i < size; i++) {
961 final Item item = mItems.get(i);
962 if (item.mIntent != null) {
Jeff Sharkey344744b2016-01-28 19:03:30 -0700963 item.mIntent.prepareToLeaveProcess(leavingPackage);
Jeff Sharkeya14acd22013-04-02 18:27:45 -0700964 }
Jeff Sharkeyfb833f32016-12-01 14:59:59 -0700965 if (item.mUri != null && leavingPackage) {
966 if (StrictMode.vmFileUriExposureEnabled()) {
967 item.mUri.checkFileUriExposed("ClipData.Item.getUri()");
968 }
969 if (StrictMode.vmContentUriWithoutPermissionEnabled()) {
970 item.mUri.checkContentUriWithoutPermission("ClipData.Item.getUri()",
971 intentFlags);
972 }
Jeff Sharkeya14acd22013-04-02 18:27:45 -0700973 }
974 }
975 }
976
Jeff Sharkeyd136e512016-03-09 22:30:56 -0700977 /** {@hide} */
978 public void prepareToEnterProcess() {
979 final int size = mItems.size();
980 for (int i = 0; i < size; i++) {
981 final Item item = mItems.get(i);
982 if (item.mIntent != null) {
983 item.mIntent.prepareToEnterProcess();
984 }
985 }
986 }
987
Nicolas Prevotd1c99b12014-07-04 16:56:17 +0100988 /** @hide */
989 public void fixUris(int contentUserHint) {
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100990 final int size = mItems.size();
991 for (int i = 0; i < size; i++) {
992 final Item item = mItems.get(i);
993 if (item.mIntent != null) {
Nicolas Prevotd1c99b12014-07-04 16:56:17 +0100994 item.mIntent.fixUris(contentUserHint);
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100995 }
996 if (item.mUri != null) {
Nicolas Prevotd1c99b12014-07-04 16:56:17 +0100997 item.mUri = maybeAddUserId(item.mUri, contentUserHint);
Nicolas Prevotd85fc722014-04-16 19:52:08 +0100998 }
999 }
1000 }
1001
Nicolas Prevotf1939902014-06-25 09:29:02 +01001002 /**
1003 * Only fixing the data field of the intents
1004 * @hide
1005 */
1006 public void fixUrisLight(int contentUserHint) {
1007 final int size = mItems.size();
1008 for (int i = 0; i < size; i++) {
1009 final Item item = mItems.get(i);
1010 if (item.mIntent != null) {
1011 Uri data = item.mIntent.getData();
1012 if (data != null) {
1013 item.mIntent.setData(maybeAddUserId(data, contentUserHint));
1014 }
1015 }
1016 if (item.mUri != null) {
1017 item.mUri = maybeAddUserId(item.mUri, contentUserHint);
1018 }
1019 }
1020 }
1021
Dianne Hackborn9f531192010-08-04 17:48:03 -07001022 @Override
Dianne Hackborn21c241e2012-03-08 13:57:23 -08001023 public String toString() {
1024 StringBuilder b = new StringBuilder(128);
1025
1026 b.append("ClipData { ");
1027 toShortString(b);
1028 b.append(" }");
1029
1030 return b.toString();
1031 }
1032
1033 /** @hide */
1034 public void toShortString(StringBuilder b) {
1035 boolean first;
1036 if (mClipDescription != null) {
1037 first = !mClipDescription.toShortString(b);
1038 } else {
1039 first = true;
1040 }
1041 if (mIcon != null) {
1042 if (!first) {
1043 b.append(' ');
1044 }
1045 first = false;
1046 b.append("I:");
1047 b.append(mIcon.getWidth());
1048 b.append('x');
1049 b.append(mIcon.getHeight());
1050 }
1051 for (int i=0; i<mItems.size(); i++) {
1052 if (!first) {
1053 b.append(' ');
1054 }
1055 first = false;
1056 b.append('{');
1057 mItems.get(i).toShortString(b);
1058 b.append('}');
1059 }
1060 }
1061
Dianne Hackbornae498722015-08-14 13:29:47 -07001062 /** @hide */
1063 public void toShortStringShortItems(StringBuilder b, boolean first) {
1064 if (mItems.size() > 0) {
1065 if (!first) {
1066 b.append(' ');
1067 }
1068 mItems.get(0).toShortString(b);
1069 if (mItems.size() > 1) {
1070 b.append(" ...");
1071 }
1072 }
1073 }
1074
Vladislav Kaznacheev9149d2b2015-12-15 12:16:28 -08001075 /** @hide */
Kweku Adams85f2fbc2017-12-18 12:04:12 -08001076 public void writeToProto(ProtoOutputStream proto, long fieldId) {
1077 final long token = proto.start(fieldId);
1078
1079 if (mClipDescription != null) {
1080 mClipDescription.writeToProto(proto, ClipDataProto.DESCRIPTION);
1081 }
1082 if (mIcon != null) {
1083 final long iToken = proto.start(ClipDataProto.ICON);
1084 proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth());
1085 proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight());
1086 proto.end(iToken);
1087 }
1088 for (int i = 0; i < mItems.size(); i++) {
1089 mItems.get(i).writeToProto(proto, ClipDataProto.ITEMS);
1090 }
1091
1092 proto.end(token);
1093 }
1094
1095 /** @hide */
Vladislav Kaznacheev9149d2b2015-12-15 12:16:28 -08001096 public void collectUris(List<Uri> out) {
1097 for (int i = 0; i < mItems.size(); ++i) {
1098 ClipData.Item item = getItemAt(i);
1099
1100 if (item.getUri() != null) {
1101 out.add(item.getUri());
1102 }
1103
1104 Intent intent = item.getIntent();
1105 if (intent != null) {
1106 if (intent.getData() != null) {
1107 out.add(intent.getData());
1108 }
1109 if (intent.getClipData() != null) {
1110 intent.getClipData().collectUris(out);
1111 }
1112 }
1113 }
1114 }
1115
Dianne Hackborn21c241e2012-03-08 13:57:23 -08001116 @Override
Dianne Hackborn9f531192010-08-04 17:48:03 -07001117 public int describeContents() {
1118 return 0;
1119 }
1120
1121 @Override
1122 public void writeToParcel(Parcel dest, int flags) {
Dianne Hackbornf834dfa2010-10-26 12:43:57 -07001123 mClipDescription.writeToParcel(dest, flags);
Dianne Hackborn9f531192010-08-04 17:48:03 -07001124 if (mIcon != null) {
1125 dest.writeInt(1);
1126 mIcon.writeToParcel(dest, flags);
1127 } else {
1128 dest.writeInt(0);
1129 }
1130 final int N = mItems.size();
1131 dest.writeInt(N);
1132 for (int i=0; i<N; i++) {
1133 Item item = mItems.get(i);
1134 TextUtils.writeToParcel(item.mText, dest, flags);
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07001135 dest.writeString(item.mHtmlText);
Dianne Hackborn9f531192010-08-04 17:48:03 -07001136 if (item.mIntent != null) {
1137 dest.writeInt(1);
1138 item.mIntent.writeToParcel(dest, flags);
1139 } else {
1140 dest.writeInt(0);
1141 }
1142 if (item.mUri != null) {
1143 dest.writeInt(1);
1144 item.mUri.writeToParcel(dest, flags);
1145 } else {
1146 dest.writeInt(0);
1147 }
1148 }
1149 }
1150
Dianne Hackborn1040dc42010-08-26 22:11:06 -07001151 ClipData(Parcel in) {
Dianne Hackbornf834dfa2010-10-26 12:43:57 -07001152 mClipDescription = new ClipDescription(in);
Dianne Hackborn9f531192010-08-04 17:48:03 -07001153 if (in.readInt() != 0) {
1154 mIcon = Bitmap.CREATOR.createFromParcel(in);
Dianne Hackborn1040dc42010-08-26 22:11:06 -07001155 } else {
1156 mIcon = null;
Dianne Hackborn9f531192010-08-04 17:48:03 -07001157 }
Dianne Hackborn21c241e2012-03-08 13:57:23 -08001158 mItems = new ArrayList<Item>();
Dianne Hackborn9f531192010-08-04 17:48:03 -07001159 final int N = in.readInt();
1160 for (int i=0; i<N; i++) {
1161 CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07001162 String htmlText = in.readString();
Dianne Hackborn9f531192010-08-04 17:48:03 -07001163 Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
1164 Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
Dianne Hackbornacb69bb2012-04-13 15:36:06 -07001165 mItems.add(new Item(text, htmlText, intent, uri));
Dianne Hackborn9f531192010-08-04 17:48:03 -07001166 }
1167 }
1168
Dianne Hackborn1040dc42010-08-26 22:11:06 -07001169 public static final Parcelable.Creator<ClipData> CREATOR =
1170 new Parcelable.Creator<ClipData>() {
Dianne Hackborn9f531192010-08-04 17:48:03 -07001171
Steve McKay57eaaf62016-06-13 12:53:31 -07001172 @Override
Dianne Hackborn1040dc42010-08-26 22:11:06 -07001173 public ClipData createFromParcel(Parcel source) {
1174 return new ClipData(source);
Dianne Hackborn9f531192010-08-04 17:48:03 -07001175 }
1176
Steve McKay57eaaf62016-06-13 12:53:31 -07001177 @Override
Dianne Hackborn1040dc42010-08-26 22:11:06 -07001178 public ClipData[] newArray(int size) {
1179 return new ClipData[size];
Dianne Hackborn9f531192010-08-04 17:48:03 -07001180 }
1181 };
1182}