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