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