1 /** 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 17 package android.content; 18 19 import static android.content.ContentProvider.maybeAddUserId; 20 import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; 21 import static android.content.ContentResolver.SCHEME_CONTENT; 22 import static android.content.ContentResolver.SCHEME_FILE; 23 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.res.AssetFileDescriptor; 26 import android.graphics.Bitmap; 27 import android.net.Uri; 28 import android.os.Build; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.StrictMode; 32 import android.text.Html; 33 import android.text.Spannable; 34 import android.text.SpannableStringBuilder; 35 import android.text.Spanned; 36 import android.text.TextUtils; 37 import android.text.style.URLSpan; 38 import android.util.Log; 39 import android.util.proto.ProtoOutputStream; 40 41 import com.android.internal.util.ArrayUtils; 42 43 import libcore.io.IoUtils; 44 45 import java.io.FileInputStream; 46 import java.io.FileNotFoundException; 47 import java.io.IOException; 48 import java.io.InputStreamReader; 49 import java.util.ArrayList; 50 import java.util.List; 51 52 /** 53 * Representation of a clipped data on the clipboard. 54 * 55 * <p>ClipData is a complex type containing one or more Item instances, 56 * each of which can hold one or more representations of an item of data. 57 * For display to the user, it also has a label.</p> 58 * 59 * <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)} 62 * 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 64 * {@link #newPlainText(CharSequence, CharSequence)}, 65 * {@link #newUri(ContentResolver, CharSequence, Uri)}, and 66 * {@link #newIntent(CharSequence, Intent)}. 67 * 68 * <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. 71 * 72 * <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 * 79 * <a name="ImplementingPaste"></a> 80 * <h3>Implementing Paste or Drop</h3> 81 * 82 * <p>To implement a paste or drop of a ClipData object into an application, 83 * 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}. 91 * In this case there is generally no need to worry about the MIME types 92 * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}, 93 * since any clip item can always be converted to a string. 94 * 95 * <p>More complicated exchanges will be done through URIs, in particular 96 * "content:" URIs. A content URI allows the recipient of a ClipData item 97 * to interact closely with the ContentProvider holding the data in order to 98 * negotiate the transfer of that data. The clip must also be filled in with 99 * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Uri)} 100 * will take care of correctly doing this. 101 * 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 * 122 * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText} 123 * 124 * <a name="ImplementingCopy"></a> 125 * <h3>Implementing Copy or Drag</h3> 126 * 127 * <p>To be the source of a clip, the application must construct a ClipData 128 * 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. 162 */ 163 public class ClipData implements Parcelable { 164 static final String[] MIMETYPES_TEXT_PLAIN = new String[] { 165 ClipDescription.MIMETYPE_TEXT_PLAIN }; 166 static final String[] MIMETYPES_TEXT_HTML = new String[] { 167 ClipDescription.MIMETYPE_TEXT_HTML }; 168 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 }; 172 173 final ClipDescription mClipDescription; 174 175 final Bitmap mIcon; 176 177 final ArrayList<Item> mItems; 178 179 /** 180 * Description of a single item in a ClipData. 181 * 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 */ 198 public static class Item { 199 final CharSequence mText; 200 final String mHtmlText; 201 final Intent mIntent; 202 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 203 Uri mUri; 204 205 /** @hide */ Item(Item other)206 public Item(Item other) { 207 mText = other.mText; 208 mHtmlText = other.mHtmlText; 209 mIntent = other.mIntent; 210 mUri = other.mUri; 211 } 212 213 /** 214 * Create an Item consisting of a single block of (possibly styled) text. 215 */ Item(CharSequence text)216 public Item(CharSequence text) { 217 mText = text; 218 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 227 * will not be done from HTML formatted text into plain text. 228 * <p><strong>Warning:</strong> Use content: URI for sharing large clip data. 229 * ClipData.Item doesn't accept an HTML text if it's larger than 800KB. 230 * </p> 231 */ Item(CharSequence text, String htmlText)232 public Item(CharSequence text, String htmlText) { 233 mText = text; 234 mHtmlText = htmlText; 235 mIntent = null; 236 mUri = null; 237 } 238 239 /** 240 * Create an Item consisting of an arbitrary Intent. 241 */ Item(Intent intent)242 public Item(Intent intent) { 243 mText = null; 244 mHtmlText = null; 245 mIntent = intent; 246 mUri = null; 247 } 248 249 /** 250 * Create an Item consisting of an arbitrary URI. 251 */ Item(Uri uri)252 public Item(Uri uri) { 253 mText = null; 254 mHtmlText = null; 255 mIntent = null; 256 mUri = uri; 257 } 258 259 /** 260 * Create a complex Item, containing multiple representations of 261 * text, Intent, and/or URI. 262 */ Item(CharSequence text, Intent intent, Uri uri)263 public Item(CharSequence text, Intent intent, Uri uri) { 264 mText = text; 265 mHtmlText = null; 266 mIntent = intent; 267 mUri = uri; 268 } 269 270 /** 271 * Create a complex Item, containing multiple representations of 272 * text, HTML text, Intent, and/or URI. If providing HTML text, you 273 * <em>must</em> supply a plain text representation as well; coercion 274 * will not be done from HTML formatted text into plain text. 275 */ Item(CharSequence text, String htmlText, Intent intent, Uri uri)276 public Item(CharSequence text, String htmlText, Intent intent, Uri uri) { 277 if (htmlText != null && text == null) { 278 throw new IllegalArgumentException( 279 "Plain text must be supplied if HTML text is supplied"); 280 } 281 mText = text; 282 mHtmlText = htmlText; 283 mIntent = intent; 284 mUri = uri; 285 } 286 287 /** 288 * Retrieve the raw text contained in this Item. 289 */ getText()290 public CharSequence getText() { 291 return mText; 292 } 293 294 /** 295 * Retrieve the raw HTML text contained in this Item. 296 */ getHtmlText()297 public String getHtmlText() { 298 return mHtmlText; 299 } 300 301 /** 302 * Retrieve the raw Intent contained in this Item. 303 */ getIntent()304 public Intent getIntent() { 305 return mIntent; 306 } 307 308 /** 309 * Retrieve the raw URI contained in this Item. 310 */ getUri()311 public Uri getUri() { 312 return mUri; 313 } 314 315 /** 316 * Turn this item into text, regardless of the type of data it 317 * actually contains. 318 * 319 * <p>The algorithm for deciding what text to return is: 320 * <ul> 321 * <li> If {@link #getText} is non-null, return that. 322 * <li> If {@link #getUri} is non-null, try to retrieve its data 323 * as a text stream from its content provider. If this succeeds, copy 324 * the text into a String and return it. If it is not a content: URI or 325 * the content provider does not supply a text representation, return 326 * the raw URI as a string. 327 * <li> If {@link #getIntent} is non-null, convert that to an intent: 328 * URI and return it. 329 * <li> Otherwise, return an empty string. 330 * </ul> 331 * 332 * @param context The caller's Context, from which its ContentResolver 333 * and other things can be retrieved. 334 * @return Returns the item's textual representation. 335 */ 336 //BEGIN_INCLUDE(coerceToText) coerceToText(Context context)337 public CharSequence coerceToText(Context context) { 338 // If this Item has an explicit textual value, simply return that. 339 CharSequence text = getText(); 340 if (text != null) { 341 return text; 342 } 343 344 // If this Item has a URI value, try using that. 345 Uri uri = getUri(); 346 if (uri != null) { 347 // First see if the URI can be opened as a plain text stream 348 // (of any sub-type). If so, this is the best textual 349 // representation for it. 350 final ContentResolver resolver = context.getContentResolver(); 351 AssetFileDescriptor descr = null; 352 FileInputStream stream = null; 353 InputStreamReader reader = null; 354 try { 355 try { 356 // Ask for a stream of the desired type. 357 descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null); 358 } catch (SecurityException e) { 359 Log.w("ClipData", "Failure opening stream", e); 360 } catch (FileNotFoundException|RuntimeException e) { 361 // Unable to open content URI as text... not really an 362 // error, just something to ignore. 363 } 364 if (descr != null) { 365 try { 366 stream = descr.createInputStream(); 367 reader = new InputStreamReader(stream, "UTF-8"); 368 369 // Got it... copy the stream into a local string and return it. 370 final StringBuilder builder = new StringBuilder(128); 371 char[] buffer = new char[8192]; 372 int len; 373 while ((len=reader.read(buffer)) > 0) { 374 builder.append(buffer, 0, len); 375 } 376 return builder.toString(); 377 } catch (IOException e) { 378 // Something bad has happened. 379 Log.w("ClipData", "Failure loading text", e); 380 return e.toString(); 381 } 382 } 383 } finally { 384 IoUtils.closeQuietly(descr); 385 IoUtils.closeQuietly(stream); 386 IoUtils.closeQuietly(reader); 387 } 388 389 // If we couldn't open the URI as a stream, use the URI itself as a textual 390 // representation (but not for "content", "android.resource" or "file" schemes). 391 final String scheme = uri.getScheme(); 392 if (SCHEME_CONTENT.equals(scheme) 393 || SCHEME_ANDROID_RESOURCE.equals(scheme) 394 || SCHEME_FILE.equals(scheme)) { 395 return ""; 396 } 397 return uri.toString(); 398 } 399 400 // Finally, if all we have is an Intent, then we can just turn that 401 // into text. Not the most user-friendly thing, but it's something. 402 Intent intent = getIntent(); 403 if (intent != null) { 404 return intent.toUri(Intent.URI_INTENT_SCHEME); 405 } 406 407 // Shouldn't get here, but just in case... 408 return ""; 409 } 410 //END_INCLUDE(coerceToText) 411 412 /** 413 * Like {@link #coerceToHtmlText(Context)}, but any text that would 414 * be returned as HTML formatting will be returned as text with 415 * style spans. 416 * @param context The caller's Context, from which its ContentResolver 417 * and other things can be retrieved. 418 * @return Returns the item's textual representation. 419 */ coerceToStyledText(Context context)420 public CharSequence coerceToStyledText(Context context) { 421 CharSequence text = getText(); 422 if (text instanceof Spanned) { 423 return text; 424 } 425 String htmlText = getHtmlText(); 426 if (htmlText != null) { 427 try { 428 CharSequence newText = Html.fromHtml(htmlText); 429 if (newText != null) { 430 return newText; 431 } 432 } catch (RuntimeException e) { 433 // If anything bad happens, we'll fall back on the plain text. 434 } 435 } 436 437 if (text != null) { 438 return text; 439 } 440 return coerceToHtmlOrStyledText(context, true); 441 } 442 443 /** 444 * Turn this item into HTML text, regardless of the type of data it 445 * actually contains. 446 * 447 * <p>The algorithm for deciding what text to return is: 448 * <ul> 449 * <li> If {@link #getHtmlText} is non-null, return that. 450 * <li> If {@link #getText} is non-null, return that, converting to 451 * valid HTML text. If this text contains style spans, 452 * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to 453 * convert them to HTML formatting. 454 * <li> If {@link #getUri} is non-null, try to retrieve its data 455 * as a text stream from its content provider. If the provider can 456 * supply text/html data, that will be preferred and returned as-is. 457 * Otherwise, any text/* data will be returned and escaped to HTML. 458 * If it is not a content: URI or the content provider does not supply 459 * a text representation, HTML text containing a link to the URI 460 * will be returned. 461 * <li> If {@link #getIntent} is non-null, convert that to an intent: 462 * URI and return as an HTML link. 463 * <li> Otherwise, return an empty string. 464 * </ul> 465 * 466 * @param context The caller's Context, from which its ContentResolver 467 * and other things can be retrieved. 468 * @return Returns the item's representation as HTML text. 469 */ coerceToHtmlText(Context context)470 public String coerceToHtmlText(Context context) { 471 // If the item has an explicit HTML value, simply return that. 472 String htmlText = getHtmlText(); 473 if (htmlText != null) { 474 return htmlText; 475 } 476 477 // If this Item has a plain text value, return it as HTML. 478 CharSequence text = getText(); 479 if (text != null) { 480 if (text instanceof Spanned) { 481 return Html.toHtml((Spanned)text); 482 } 483 return Html.escapeHtml(text); 484 } 485 486 text = coerceToHtmlOrStyledText(context, false); 487 return text != null ? text.toString() : null; 488 } 489 coerceToHtmlOrStyledText(Context context, boolean styled)490 private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) { 491 // If this Item has a URI value, try using that. 492 if (mUri != null) { 493 494 // Check to see what data representations the content 495 // provider supports. We would like HTML text, but if that 496 // is not possible we'll live with plan text. 497 String[] types = null; 498 try { 499 types = context.getContentResolver().getStreamTypes(mUri, "text/*"); 500 } catch (SecurityException e) { 501 // No read permission for mUri, assume empty stream types list. 502 } 503 boolean hasHtml = false; 504 boolean hasText = false; 505 if (types != null) { 506 for (String type : types) { 507 if ("text/html".equals(type)) { 508 hasHtml = true; 509 } else if (type.startsWith("text/")) { 510 hasText = true; 511 } 512 } 513 } 514 515 // If the provider can serve data we can use, open and load it. 516 if (hasHtml || hasText) { 517 FileInputStream stream = null; 518 try { 519 // Ask for a stream of the desired type. 520 AssetFileDescriptor descr = context.getContentResolver() 521 .openTypedAssetFileDescriptor(mUri, 522 hasHtml ? "text/html" : "text/plain", null); 523 stream = descr.createInputStream(); 524 InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); 525 526 // Got it... copy the stream into a local string and return it. 527 StringBuilder builder = new StringBuilder(128); 528 char[] buffer = new char[8192]; 529 int len; 530 while ((len=reader.read(buffer)) > 0) { 531 builder.append(buffer, 0, len); 532 } 533 String text = builder.toString(); 534 if (hasHtml) { 535 if (styled) { 536 // We loaded HTML formatted text and the caller 537 // want styled text, convert it. 538 try { 539 CharSequence newText = Html.fromHtml(text); 540 return newText != null ? newText : text; 541 } catch (RuntimeException e) { 542 return text; 543 } 544 } else { 545 // We loaded HTML formatted text and that is what 546 // the caller wants, just return it. 547 return text.toString(); 548 } 549 } 550 if (styled) { 551 // We loaded plain text and the caller wants styled 552 // text, that is all we have so return it. 553 return text; 554 } else { 555 // We loaded plain text and the caller wants HTML 556 // text, escape it for HTML. 557 return Html.escapeHtml(text); 558 } 559 560 } catch (SecurityException e) { 561 Log.w("ClipData", "Failure opening stream", e); 562 563 } catch (FileNotFoundException e) { 564 // Unable to open content URI as text... not really an 565 // error, just something to ignore. 566 567 } catch (IOException e) { 568 // Something bad has happened. 569 Log.w("ClipData", "Failure loading text", e); 570 return Html.escapeHtml(e.toString()); 571 572 } finally { 573 if (stream != null) { 574 try { 575 stream.close(); 576 } catch (IOException e) { 577 } 578 } 579 } 580 } 581 582 // If we couldn't open the URI as a stream, use the URI itself as a textual 583 // representation (but not for "content", "android.resource" or "file" schemes). 584 final String scheme = mUri.getScheme(); 585 if (SCHEME_CONTENT.equals(scheme) 586 || SCHEME_ANDROID_RESOURCE.equals(scheme) 587 || SCHEME_FILE.equals(scheme)) { 588 return ""; 589 } 590 591 if (styled) { 592 return uriToStyledText(mUri.toString()); 593 } else { 594 return uriToHtml(mUri.toString()); 595 } 596 } 597 598 // Finally, if all we have is an Intent, then we can just turn that 599 // into text. Not the most user-friendly thing, but it's something. 600 if (mIntent != null) { 601 if (styled) { 602 return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 603 } else { 604 return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 605 } 606 } 607 608 // Shouldn't get here, but just in case... 609 return ""; 610 } 611 uriToHtml(String uri)612 private String uriToHtml(String uri) { 613 StringBuilder builder = new StringBuilder(256); 614 builder.append("<a href=\""); 615 builder.append(Html.escapeHtml(uri)); 616 builder.append("\">"); 617 builder.append(Html.escapeHtml(uri)); 618 builder.append("</a>"); 619 return builder.toString(); 620 } 621 uriToStyledText(String uri)622 private CharSequence uriToStyledText(String uri) { 623 SpannableStringBuilder builder = new SpannableStringBuilder(); 624 builder.append(uri); 625 builder.setSpan(new URLSpan(uri), 0, builder.length(), 626 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 627 return builder; 628 } 629 630 @Override toString()631 public String toString() { 632 StringBuilder b = new StringBuilder(128); 633 634 b.append("ClipData.Item { "); 635 toShortString(b); 636 b.append(" }"); 637 638 return b.toString(); 639 } 640 641 /** @hide */ toShortString(StringBuilder b)642 public void toShortString(StringBuilder b) { 643 if (mHtmlText != null) { 644 b.append("H:"); 645 b.append(mHtmlText); 646 } else if (mText != null) { 647 b.append("T:"); 648 b.append(mText); 649 } else if (mUri != null) { 650 b.append("U:"); 651 b.append(mUri); 652 } else if (mIntent != null) { 653 b.append("I:"); 654 mIntent.toShortString(b, true, true, true, true); 655 } else { 656 b.append("NULL"); 657 } 658 } 659 660 /** @hide */ toShortSummaryString(StringBuilder b)661 public void toShortSummaryString(StringBuilder b) { 662 if (mHtmlText != null) { 663 b.append("HTML"); 664 } else if (mText != null) { 665 b.append("TEXT"); 666 } else if (mUri != null) { 667 b.append("U:"); 668 b.append(mUri); 669 } else if (mIntent != null) { 670 b.append("I:"); 671 mIntent.toShortString(b, true, true, true, true); 672 } else { 673 b.append("NULL"); 674 } 675 } 676 677 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)678 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 679 final long token = proto.start(fieldId); 680 681 if (mHtmlText != null) { 682 proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText); 683 } else if (mText != null) { 684 proto.write(ClipDataProto.Item.TEXT, mText.toString()); 685 } else if (mUri != null) { 686 proto.write(ClipDataProto.Item.URI, mUri.toString()); 687 } else if (mIntent != null) { 688 mIntent.dumpDebug(proto, ClipDataProto.Item.INTENT, true, true, true, true); 689 } else { 690 proto.write(ClipDataProto.Item.NOTHING, true); 691 } 692 693 proto.end(token); 694 } 695 } 696 697 /** 698 * Create a new clip. 699 * 700 * @param label Label to show to the user describing this clip. 701 * @param mimeTypes An array of MIME types this data is available as. 702 * @param item The contents of the first item in the clip. 703 */ ClipData(CharSequence label, String[] mimeTypes, Item item)704 public ClipData(CharSequence label, String[] mimeTypes, Item item) { 705 mClipDescription = new ClipDescription(label, mimeTypes); 706 if (item == null) { 707 throw new NullPointerException("item is null"); 708 } 709 mIcon = null; 710 mItems = new ArrayList<Item>(); 711 mItems.add(item); 712 } 713 714 /** 715 * Create a new clip. 716 * 717 * @param description The ClipDescription describing the clip contents. 718 * @param item The contents of the first item in the clip. 719 */ ClipData(ClipDescription description, Item item)720 public ClipData(ClipDescription description, Item item) { 721 mClipDescription = description; 722 if (item == null) { 723 throw new NullPointerException("item is null"); 724 } 725 mIcon = null; 726 mItems = new ArrayList<Item>(); 727 mItems.add(item); 728 } 729 730 /** 731 * Create a new clip. 732 * 733 * @param description The ClipDescription describing the clip contents. 734 * @param items The items in the clip. Not that a defensive copy of this 735 * list is not made, so caution should be taken to ensure the 736 * list is not available for further modification. 737 * @hide 738 */ ClipData(ClipDescription description, ArrayList<Item> items)739 public ClipData(ClipDescription description, ArrayList<Item> items) { 740 mClipDescription = description; 741 if (items == null) { 742 throw new NullPointerException("item is null"); 743 } 744 mIcon = null; 745 mItems = items; 746 } 747 748 /** 749 * Create a new clip that is a copy of another clip. This does a deep-copy 750 * of all items in the clip. 751 * 752 * @param other The existing ClipData that is to be copied. 753 */ ClipData(ClipData other)754 public ClipData(ClipData other) { 755 mClipDescription = other.mClipDescription; 756 mIcon = other.mIcon; 757 mItems = new ArrayList<Item>(other.mItems); 758 } 759 760 /** 761 * Create a new ClipData holding data of the type 762 * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. 763 * 764 * @param label User-visible label for the clip data. 765 * @param text The actual text in the clip. 766 * @return Returns a new ClipData containing the specified data. 767 */ newPlainText(CharSequence label, CharSequence text)768 static public ClipData newPlainText(CharSequence label, CharSequence text) { 769 Item item = new Item(text); 770 return new ClipData(label, MIMETYPES_TEXT_PLAIN, item); 771 } 772 773 /** 774 * Create a new ClipData holding data of the type 775 * {@link ClipDescription#MIMETYPE_TEXT_HTML}. 776 * 777 * @param label User-visible label for the clip data. 778 * @param text The text of clip as plain text, for receivers that don't 779 * handle HTML. This is required. 780 * @param htmlText The actual HTML text in the clip. 781 * @return Returns a new ClipData containing the specified data. 782 */ newHtmlText(CharSequence label, CharSequence text, String htmlText)783 static public ClipData newHtmlText(CharSequence label, CharSequence text, 784 String htmlText) { 785 Item item = new Item(text, htmlText); 786 return new ClipData(label, MIMETYPES_TEXT_HTML, item); 787 } 788 789 /** 790 * Create a new ClipData holding an Intent with MIME type 791 * {@link ClipDescription#MIMETYPE_TEXT_INTENT}. 792 * 793 * @param label User-visible label for the clip data. 794 * @param intent The actual Intent in the clip. 795 * @return Returns a new ClipData containing the specified data. 796 */ newIntent(CharSequence label, Intent intent)797 static public ClipData newIntent(CharSequence label, Intent intent) { 798 Item item = new Item(intent); 799 return new ClipData(label, MIMETYPES_TEXT_INTENT, item); 800 } 801 802 /** 803 * Create a new ClipData holding a URI. If the URI is a content: URI, 804 * this will query the content provider for the MIME type of its data and 805 * use that as the MIME type. Otherwise, it will use the MIME type 806 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 807 * 808 * @param resolver ContentResolver used to get information about the URI. 809 * @param label User-visible label for the clip data. 810 * @param uri The URI in the clip. 811 * @return Returns a new ClipData containing the specified data. 812 */ newUri(ContentResolver resolver, CharSequence label, Uri uri)813 static public ClipData newUri(ContentResolver resolver, CharSequence label, 814 Uri uri) { 815 Item item = new Item(uri); 816 String[] mimeTypes = getMimeTypes(resolver, uri); 817 return new ClipData(label, mimeTypes, item); 818 } 819 820 /** 821 * Finds all applicable MIME types for a given URI. 822 * 823 * @param resolver ContentResolver used to get information about the URI. 824 * @param uri The URI. 825 * @return Returns an array of MIME types. 826 */ getMimeTypes(ContentResolver resolver, Uri uri)827 private static String[] getMimeTypes(ContentResolver resolver, Uri uri) { 828 String[] mimeTypes = null; 829 if (SCHEME_CONTENT.equals(uri.getScheme())) { 830 String realType = resolver.getType(uri); 831 mimeTypes = resolver.getStreamTypes(uri, "*/*"); 832 if (realType != null) { 833 if (mimeTypes == null) { 834 mimeTypes = new String[] { realType }; 835 } else if (!ArrayUtils.contains(mimeTypes, realType)) { 836 String[] tmp = new String[mimeTypes.length + 1]; 837 tmp[0] = realType; 838 System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length); 839 mimeTypes = tmp; 840 } 841 } 842 } 843 if (mimeTypes == null) { 844 mimeTypes = MIMETYPES_TEXT_URILIST; 845 } 846 return mimeTypes; 847 } 848 849 /** 850 * Create a new ClipData holding an URI with MIME type 851 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 852 * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing 853 * is inferred about the URI -- if it is a content: URI holding a bitmap, 854 * the reported type will still be uri-list. Use this with care! 855 * 856 * @param label User-visible label for the clip data. 857 * @param uri The URI in the clip. 858 * @return Returns a new ClipData containing the specified data. 859 */ newRawUri(CharSequence label, Uri uri)860 static public ClipData newRawUri(CharSequence label, Uri uri) { 861 Item item = new Item(uri); 862 return new ClipData(label, MIMETYPES_TEXT_URILIST, item); 863 } 864 865 /** 866 * Return the {@link ClipDescription} associated with this data, describing 867 * what it contains. 868 */ getDescription()869 public ClipDescription getDescription() { 870 return mClipDescription; 871 } 872 873 /** 874 * Add a new Item to the overall ClipData container. 875 * <p> This method will <em>not</em> update the list of available MIME types in the 876 * {@link ClipDescription}. It should be used only when adding items which do not add new 877 * MIME types to this clip. If this is not the case, use {@link #addItem(ContentResolver, Item)} 878 * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types. 879 * @param item Item to be added. 880 */ addItem(Item item)881 public void addItem(Item item) { 882 if (item == null) { 883 throw new NullPointerException("item is null"); 884 } 885 mItems.add(item); 886 } 887 888 /** 889 * Add a new Item to the overall ClipData container. 890 * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types 891 * in the {@link ClipDescription}. 892 * @param resolver ContentResolver used to get information about the URI possibly contained in 893 * the item. 894 * @param item Item to be added. 895 */ addItem(ContentResolver resolver, Item item)896 public void addItem(ContentResolver resolver, Item item) { 897 addItem(item); 898 899 if (item.getHtmlText() != null) { 900 mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML); 901 } else if (item.getText() != null) { 902 mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN); 903 } 904 905 if (item.getIntent() != null) { 906 mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT); 907 } 908 909 if (item.getUri() != null) { 910 mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri())); 911 } 912 } 913 914 /** @hide */ 915 @UnsupportedAppUsage getIcon()916 public Bitmap getIcon() { 917 return mIcon; 918 } 919 920 /** 921 * Return the number of items in the clip data. 922 */ getItemCount()923 public int getItemCount() { 924 return mItems.size(); 925 } 926 927 /** 928 * Return a single item inside of the clip data. The index can range 929 * from 0 to {@link #getItemCount()}-1. 930 */ getItemAt(int index)931 public Item getItemAt(int index) { 932 return mItems.get(index); 933 } 934 935 /** @hide */ setItemAt(int index, Item item)936 public void setItemAt(int index, Item item) { 937 mItems.set(index, item); 938 } 939 940 /** 941 * Prepare this {@link ClipData} to leave an app process. 942 * 943 * @hide 944 */ prepareToLeaveProcess(boolean leavingPackage)945 public void prepareToLeaveProcess(boolean leavingPackage) { 946 // Assume that callers are going to be granting permissions 947 prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION); 948 } 949 950 /** 951 * Prepare this {@link ClipData} to leave an app process. 952 * 953 * @hide 954 */ prepareToLeaveProcess(boolean leavingPackage, int intentFlags)955 public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) { 956 final int size = mItems.size(); 957 for (int i = 0; i < size; i++) { 958 final Item item = mItems.get(i); 959 if (item.mIntent != null) { 960 item.mIntent.prepareToLeaveProcess(leavingPackage); 961 } 962 if (item.mUri != null && leavingPackage) { 963 if (StrictMode.vmFileUriExposureEnabled()) { 964 item.mUri.checkFileUriExposed("ClipData.Item.getUri()"); 965 } 966 if (StrictMode.vmContentUriWithoutPermissionEnabled()) { 967 item.mUri.checkContentUriWithoutPermission("ClipData.Item.getUri()", 968 intentFlags); 969 } 970 } 971 } 972 } 973 974 /** {@hide} */ prepareToEnterProcess()975 public void prepareToEnterProcess() { 976 final int size = mItems.size(); 977 for (int i = 0; i < size; i++) { 978 final Item item = mItems.get(i); 979 if (item.mIntent != null) { 980 item.mIntent.prepareToEnterProcess(); 981 } 982 } 983 } 984 985 /** @hide */ fixUris(int contentUserHint)986 public void fixUris(int contentUserHint) { 987 final int size = mItems.size(); 988 for (int i = 0; i < size; i++) { 989 final Item item = mItems.get(i); 990 if (item.mIntent != null) { 991 item.mIntent.fixUris(contentUserHint); 992 } 993 if (item.mUri != null) { 994 item.mUri = maybeAddUserId(item.mUri, contentUserHint); 995 } 996 } 997 } 998 999 /** 1000 * Only fixing the data field of the intents 1001 * @hide 1002 */ fixUrisLight(int contentUserHint)1003 public void fixUrisLight(int contentUserHint) { 1004 final int size = mItems.size(); 1005 for (int i = 0; i < size; i++) { 1006 final Item item = mItems.get(i); 1007 if (item.mIntent != null) { 1008 Uri data = item.mIntent.getData(); 1009 if (data != null) { 1010 item.mIntent.setData(maybeAddUserId(data, contentUserHint)); 1011 } 1012 } 1013 if (item.mUri != null) { 1014 item.mUri = maybeAddUserId(item.mUri, contentUserHint); 1015 } 1016 } 1017 } 1018 1019 @Override toString()1020 public String toString() { 1021 StringBuilder b = new StringBuilder(128); 1022 1023 b.append("ClipData { "); 1024 toShortString(b); 1025 b.append(" }"); 1026 1027 return b.toString(); 1028 } 1029 1030 /** @hide */ toShortString(StringBuilder b)1031 public void toShortString(StringBuilder b) { 1032 boolean first; 1033 if (mClipDescription != null) { 1034 first = !mClipDescription.toShortString(b); 1035 } else { 1036 first = true; 1037 } 1038 if (mIcon != null) { 1039 if (!first) { 1040 b.append(' '); 1041 } 1042 first = false; 1043 b.append("I:"); 1044 b.append(mIcon.getWidth()); 1045 b.append('x'); 1046 b.append(mIcon.getHeight()); 1047 } 1048 for (int i=0; i<mItems.size(); i++) { 1049 if (!first) { 1050 b.append(' '); 1051 } 1052 first = false; 1053 b.append('{'); 1054 mItems.get(i).toShortString(b); 1055 b.append('}'); 1056 } 1057 } 1058 1059 /** @hide */ toShortStringShortItems(StringBuilder b, boolean first)1060 public void toShortStringShortItems(StringBuilder b, boolean first) { 1061 if (mItems.size() > 0) { 1062 if (!first) { 1063 b.append(' '); 1064 } 1065 for (int i=0; i<mItems.size(); i++) { 1066 b.append("{...}"); 1067 } 1068 } 1069 } 1070 1071 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)1072 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 1073 final long token = proto.start(fieldId); 1074 1075 if (mClipDescription != null) { 1076 mClipDescription.dumpDebug(proto, ClipDataProto.DESCRIPTION); 1077 } 1078 if (mIcon != null) { 1079 final long iToken = proto.start(ClipDataProto.ICON); 1080 proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth()); 1081 proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight()); 1082 proto.end(iToken); 1083 } 1084 for (int i = 0; i < mItems.size(); i++) { 1085 mItems.get(i).dumpDebug(proto, ClipDataProto.ITEMS); 1086 } 1087 1088 proto.end(token); 1089 } 1090 1091 /** @hide */ collectUris(List<Uri> out)1092 public void collectUris(List<Uri> out) { 1093 for (int i = 0; i < mItems.size(); ++i) { 1094 ClipData.Item item = getItemAt(i); 1095 1096 if (item.getUri() != null) { 1097 out.add(item.getUri()); 1098 } 1099 1100 Intent intent = item.getIntent(); 1101 if (intent != null) { 1102 if (intent.getData() != null) { 1103 out.add(intent.getData()); 1104 } 1105 if (intent.getClipData() != null) { 1106 intent.getClipData().collectUris(out); 1107 } 1108 } 1109 } 1110 } 1111 1112 @Override describeContents()1113 public int describeContents() { 1114 return 0; 1115 } 1116 1117 @Override writeToParcel(Parcel dest, int flags)1118 public void writeToParcel(Parcel dest, int flags) { 1119 mClipDescription.writeToParcel(dest, flags); 1120 if (mIcon != null) { 1121 dest.writeInt(1); 1122 mIcon.writeToParcel(dest, flags); 1123 } else { 1124 dest.writeInt(0); 1125 } 1126 final int N = mItems.size(); 1127 dest.writeInt(N); 1128 for (int i=0; i<N; i++) { 1129 Item item = mItems.get(i); 1130 TextUtils.writeToParcel(item.mText, dest, flags); 1131 dest.writeString8(item.mHtmlText); 1132 if (item.mIntent != null) { 1133 dest.writeInt(1); 1134 item.mIntent.writeToParcel(dest, flags); 1135 } else { 1136 dest.writeInt(0); 1137 } 1138 if (item.mUri != null) { 1139 dest.writeInt(1); 1140 item.mUri.writeToParcel(dest, flags); 1141 } else { 1142 dest.writeInt(0); 1143 } 1144 } 1145 } 1146 ClipData(Parcel in)1147 ClipData(Parcel in) { 1148 mClipDescription = new ClipDescription(in); 1149 if (in.readInt() != 0) { 1150 mIcon = Bitmap.CREATOR.createFromParcel(in); 1151 } else { 1152 mIcon = null; 1153 } 1154 mItems = new ArrayList<Item>(); 1155 final int N = in.readInt(); 1156 for (int i=0; i<N; i++) { 1157 CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1158 String htmlText = in.readString8(); 1159 Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null; 1160 Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null; 1161 mItems.add(new Item(text, htmlText, intent, uri)); 1162 } 1163 } 1164 1165 public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR = 1166 new Parcelable.Creator<ClipData>() { 1167 1168 @Override 1169 public ClipData createFromParcel(Parcel source) { 1170 return new ClipData(source); 1171 } 1172 1173 @Override 1174 public ClipData[] newArray(int size) { 1175 return new ClipData[size]; 1176 } 1177 }; 1178 } 1179