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 /** 225 * A builder for a ClipData Item. 226 */ 227 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 228 public static final class Builder { 229 private CharSequence mText; 230 private String mHtmlText; 231 private Intent mIntent; 232 private IntentSender mIntentSender; 233 private Uri mUri; 234 235 /** 236 * Sets the text for the item to be constructed. 237 */ 238 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 239 @NonNull setText(@ullable CharSequence text)240 public Builder setText(@Nullable CharSequence text) { 241 mText = text; 242 return this; 243 } 244 245 /** 246 * Sets the HTML text for the item to be constructed. 247 */ 248 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 249 @NonNull setHtmlText(@ullable String htmlText)250 public Builder setHtmlText(@Nullable String htmlText) { 251 mHtmlText = htmlText; 252 return this; 253 } 254 255 /** 256 * Sets the Intent for the item to be constructed. 257 */ 258 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 259 @NonNull setIntent(@ullable Intent intent)260 public Builder setIntent(@Nullable Intent intent) { 261 mIntent = intent; 262 return this; 263 } 264 265 /** 266 * Sets the {@link IntentSender} for the item to be constructed. To prevent receiving 267 * apps from improperly manipulating the intent to launch another activity as this 268 * caller, the provided IntentSender must be immutable. 269 * 270 * If there is a fixed lifetime for this ClipData (ie. for drag and drop), the system 271 * will cancel the IntentSender when it is no longer used. 272 */ 273 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 274 @NonNull setIntentSender(@ullable IntentSender intentSender)275 public Builder setIntentSender(@Nullable IntentSender intentSender) { 276 if (intentSender != null && !intentSender.isImmutable()) { 277 throw new IllegalArgumentException("Expected intent sender to be immutable"); 278 } 279 mIntentSender = intentSender; 280 return this; 281 } 282 283 /** 284 * Sets the URI for the item to be constructed. 285 */ 286 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 287 @NonNull setUri(@ullable Uri uri)288 public Builder setUri(@Nullable Uri uri) { 289 mUri = uri; 290 return this; 291 } 292 293 /** 294 * Constructs a new Item with the properties set on this builder. 295 */ 296 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 297 @NonNull build()298 public Item build() { 299 return new Item(mText, mHtmlText, mIntent, mIntentSender, mUri); 300 } 301 } 302 303 304 /** @hide */ Item(Item other)305 public Item(Item other) { 306 mText = other.mText; 307 mHtmlText = other.mHtmlText; 308 mIntent = other.mIntent; 309 mIntentSender = other.mIntentSender; 310 mUri = other.mUri; 311 mActivityInfo = other.mActivityInfo; 312 mTextLinks = other.mTextLinks; 313 } 314 315 /** 316 * Create an Item consisting of a single block of (possibly styled) text. 317 */ Item(CharSequence text)318 public Item(CharSequence text) { 319 this(text, null, null, null, null); 320 } 321 322 /** 323 * Create an Item consisting of a single block of (possibly styled) text, 324 * with an alternative HTML formatted representation. You <em>must</em> 325 * supply a plain text representation in addition to HTML text; coercion 326 * will not be done from HTML formatted text into plain text. 327 * <p><strong>Warning:</strong> Use content: URI for sharing large clip data. 328 * ClipData.Item doesn't accept an HTML text if it's larger than 800KB. 329 * </p> 330 */ Item(CharSequence text, String htmlText)331 public Item(CharSequence text, String htmlText) { 332 this(text, htmlText, null, null, null); 333 } 334 335 /** 336 * Create an Item consisting of an arbitrary Intent. 337 */ Item(Intent intent)338 public Item(Intent intent) { 339 this(null, null, intent, null, null); 340 } 341 342 /** 343 * Create an Item consisting of an arbitrary URI. 344 */ Item(Uri uri)345 public Item(Uri uri) { 346 this(null, null, null, null, uri); 347 } 348 349 /** 350 * Create a complex Item, containing multiple representations of 351 * text, Intent, and/or URI. 352 */ Item(CharSequence text, Intent intent, Uri uri)353 public Item(CharSequence text, Intent intent, Uri uri) { 354 this(text, null, intent, null, uri); 355 } 356 357 /** 358 * Create a complex Item, containing multiple representations of 359 * text, HTML text, Intent, and/or URI. If providing HTML text, you 360 * <em>must</em> supply a plain text representation as well; coercion 361 * will not be done from HTML formatted text into plain text. 362 */ Item(CharSequence text, String htmlText, Intent intent, Uri uri)363 public Item(CharSequence text, String htmlText, Intent intent, Uri uri) { 364 this(text, htmlText, intent, null, uri); 365 } 366 367 /** 368 * Builder ctor. 369 */ Item(CharSequence text, String htmlText, Intent intent, IntentSender intentSender, Uri uri)370 private Item(CharSequence text, String htmlText, Intent intent, IntentSender intentSender, 371 Uri uri) { 372 if (htmlText != null && text == null) { 373 throw new IllegalArgumentException( 374 "Plain text must be supplied if HTML text is supplied"); 375 } 376 mText = text; 377 mHtmlText = htmlText; 378 mIntent = intent; 379 mIntentSender = intentSender; 380 mUri = uri; 381 } 382 383 /** 384 * Retrieve the raw text contained in this Item. 385 */ getText()386 public CharSequence getText() { 387 return mText; 388 } 389 390 /** 391 * Retrieve the raw HTML text contained in this Item. 392 */ getHtmlText()393 public String getHtmlText() { 394 return mHtmlText; 395 } 396 397 /** 398 * Retrieve the raw Intent contained in this Item. 399 */ getIntent()400 public Intent getIntent() { 401 return mIntent; 402 } 403 404 /** 405 * Returns the {@link IntentSender} in this Item. 406 */ 407 @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS) 408 @Nullable getIntentSender()409 public IntentSender getIntentSender() { 410 return mIntentSender; 411 } 412 413 /** 414 * Retrieve the raw URI contained in this Item. 415 */ getUri()416 public Uri getUri() { 417 return mUri; 418 } 419 420 /** 421 * Retrieve the activity info contained in this Item. 422 * @hide 423 */ getActivityInfo()424 public ActivityInfo getActivityInfo() { 425 return mActivityInfo; 426 } 427 428 /** 429 * Updates the activity info for in this Item. 430 * @hide 431 */ setActivityInfo(ActivityInfo info)432 public void setActivityInfo(ActivityInfo info) { 433 mActivityInfo = info; 434 } 435 436 /** 437 * Returns the results of text classification run on the raw text contained in this item, 438 * if it was performed, and if any entities were found in the text. Classification is 439 * generally only performed on the first item in clip data, and only if the text is below a 440 * certain length. 441 * 442 * <p>Returns {@code null} if classification was not performed, or if no entities were 443 * found in the text. 444 * 445 * @see ClipDescription#getConfidenceScore(String) 446 */ 447 @Nullable getTextLinks()448 public TextLinks getTextLinks() { 449 return mTextLinks; 450 } 451 452 /** 453 * @hide 454 */ setTextLinks(TextLinks textLinks)455 public void setTextLinks(TextLinks textLinks) { 456 mTextLinks = textLinks; 457 } 458 459 /** 460 * Turn this item into text, regardless of the type of data it 461 * actually contains. 462 * 463 * <p>The algorithm for deciding what text to return is: 464 * <ul> 465 * <li> If {@link #getText} is non-null, return that. 466 * <li> If {@link #getUri} is non-null, try to retrieve its data 467 * as a text stream from its content provider. If this succeeds, copy 468 * the text into a String and return it. If it is not a content: URI or 469 * the content provider does not supply a text representation, return 470 * the raw URI as a string. 471 * <li> If {@link #getIntent} is non-null, convert that to an intent: 472 * URI and return it. 473 * <li> Otherwise, return an empty string. 474 * </ul> 475 * 476 * @param context The caller's Context, from which its ContentResolver 477 * and other things can be retrieved. 478 * @return Returns the item's textual representation. 479 */ 480 //BEGIN_INCLUDE(coerceToText) coerceToText(Context context)481 public CharSequence coerceToText(Context context) { 482 // If this Item has an explicit textual value, simply return that. 483 CharSequence text = getText(); 484 if (text != null) { 485 return text; 486 } 487 488 // Gracefully handle cases where resolver isn't available 489 ContentResolver resolver = null; 490 try { 491 resolver = context.getContentResolver(); 492 } catch (Exception e) { 493 Log.w(TAG, "Failed to obtain ContentResolver: " + e); 494 } 495 496 // If this Item has a URI value, try using that. 497 Uri uri = getUri(); 498 if (uri != null && resolver != null) { 499 // First see if the URI can be opened as a plain text stream 500 // (of any sub-type). If so, this is the best textual 501 // representation for it. 502 AssetFileDescriptor descr = null; 503 FileInputStream stream = null; 504 InputStreamReader reader = null; 505 try { 506 try { 507 // Ask for a stream of the desired type. 508 descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null); 509 } catch (SecurityException e) { 510 Log.w(TAG, "Failure opening stream", e); 511 } catch (FileNotFoundException|RuntimeException e) { 512 // Unable to open content URI as text... not really an 513 // error, just something to ignore. 514 } 515 if (descr != null) { 516 try { 517 stream = descr.createInputStream(); 518 reader = new InputStreamReader(stream, "UTF-8"); 519 520 // Got it... copy the stream into a local string and return it. 521 final StringBuilder builder = new StringBuilder(128); 522 char[] buffer = new char[8192]; 523 int len; 524 while ((len=reader.read(buffer)) > 0) { 525 builder.append(buffer, 0, len); 526 } 527 return builder.toString(); 528 } catch (IOException e) { 529 // Something bad has happened. 530 Log.w(TAG, "Failure loading text", e); 531 return e.toString(); 532 } 533 } 534 } finally { 535 IoUtils.closeQuietly(descr); 536 IoUtils.closeQuietly(stream); 537 IoUtils.closeQuietly(reader); 538 } 539 } 540 if (uri != null) { 541 // If we couldn't open the URI as a stream, use the URI itself as a textual 542 // representation (but not for "content", "android.resource" or "file" schemes). 543 final String scheme = uri.getScheme(); 544 if (SCHEME_CONTENT.equals(scheme) 545 || SCHEME_ANDROID_RESOURCE.equals(scheme) 546 || SCHEME_FILE.equals(scheme)) { 547 return ""; 548 } 549 return uri.toString(); 550 } 551 552 // Finally, if all we have is an Intent, then we can just turn that 553 // into text. Not the most user-friendly thing, but it's something. 554 Intent intent = getIntent(); 555 if (intent != null) { 556 return intent.toUri(Intent.URI_INTENT_SCHEME); 557 } 558 559 // Shouldn't get here, but just in case... 560 return ""; 561 } 562 //END_INCLUDE(coerceToText) 563 564 /** 565 * Like {@link #coerceToHtmlText(Context)}, but any text that would 566 * be returned as HTML formatting will be returned as text with 567 * style spans. 568 * @param context The caller's Context, from which its ContentResolver 569 * and other things can be retrieved. 570 * @return Returns the item's textual representation. 571 */ 572 @android.ravenwood.annotation.RavenwoodThrow coerceToStyledText(Context context)573 public CharSequence coerceToStyledText(Context context) { 574 CharSequence text = getText(); 575 if (text instanceof Spanned) { 576 return text; 577 } 578 String htmlText = getHtmlText(); 579 if (htmlText != null) { 580 try { 581 CharSequence newText = Html.fromHtml(htmlText); 582 if (newText != null) { 583 return newText; 584 } 585 } catch (RuntimeException e) { 586 // If anything bad happens, we'll fall back on the plain text. 587 } 588 } 589 590 if (text != null) { 591 return text; 592 } 593 return coerceToHtmlOrStyledText(context, true); 594 } 595 596 /** 597 * Turn this item into HTML text, regardless of the type of data it 598 * actually contains. 599 * 600 * <p>The algorithm for deciding what text to return is: 601 * <ul> 602 * <li> If {@link #getHtmlText} is non-null, return that. 603 * <li> If {@link #getText} is non-null, return that, converting to 604 * valid HTML text. If this text contains style spans, 605 * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to 606 * convert them to HTML formatting. 607 * <li> If {@link #getUri} is non-null, try to retrieve its data 608 * as a text stream from its content provider. If the provider can 609 * supply text/html data, that will be preferred and returned as-is. 610 * Otherwise, any text/* data will be returned and escaped to HTML. 611 * If it is not a content: URI or the content provider does not supply 612 * a text representation, HTML text containing a link to the URI 613 * will be returned. 614 * <li> If {@link #getIntent} is non-null, convert that to an intent: 615 * URI and return as an HTML link. 616 * <li> Otherwise, return an empty string. 617 * </ul> 618 * 619 * @param context The caller's Context, from which its ContentResolver 620 * and other things can be retrieved. 621 * @return Returns the item's representation as HTML text. 622 */ 623 @android.ravenwood.annotation.RavenwoodThrow coerceToHtmlText(Context context)624 public String coerceToHtmlText(Context context) { 625 // If the item has an explicit HTML value, simply return that. 626 String htmlText = getHtmlText(); 627 if (htmlText != null) { 628 return htmlText; 629 } 630 631 // If this Item has a plain text value, return it as HTML. 632 CharSequence text = getText(); 633 if (text != null) { 634 if (text instanceof Spanned) { 635 return Html.toHtml((Spanned)text); 636 } 637 return Html.escapeHtml(text); 638 } 639 640 text = coerceToHtmlOrStyledText(context, false); 641 return text != null ? text.toString() : null; 642 } 643 644 @android.ravenwood.annotation.RavenwoodThrow coerceToHtmlOrStyledText(Context context, boolean styled)645 private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) { 646 // If this Item has a URI value, try using that. 647 if (mUri != null) { 648 649 // Check to see what data representations the content 650 // provider supports. We would like HTML text, but if that 651 // is not possible we'll live with plan text. 652 String[] types = null; 653 try { 654 types = context.getContentResolver().getStreamTypes(mUri, "text/*"); 655 } catch (SecurityException e) { 656 // No read permission for mUri, assume empty stream types list. 657 } 658 boolean hasHtml = false; 659 boolean hasText = false; 660 if (types != null) { 661 for (String type : types) { 662 if ("text/html".equals(type)) { 663 hasHtml = true; 664 } else if (type.startsWith("text/")) { 665 hasText = true; 666 } 667 } 668 } 669 670 // If the provider can serve data we can use, open and load it. 671 if (hasHtml || hasText) { 672 FileInputStream stream = null; 673 try { 674 // Ask for a stream of the desired type. 675 AssetFileDescriptor descr = context.getContentResolver() 676 .openTypedAssetFileDescriptor(mUri, 677 hasHtml ? "text/html" : "text/plain", null); 678 stream = descr.createInputStream(); 679 InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); 680 681 // Got it... copy the stream into a local string and return it. 682 StringBuilder builder = new StringBuilder(128); 683 char[] buffer = new char[8192]; 684 int len; 685 while ((len=reader.read(buffer)) > 0) { 686 builder.append(buffer, 0, len); 687 } 688 String text = builder.toString(); 689 if (hasHtml) { 690 if (styled) { 691 // We loaded HTML formatted text and the caller 692 // want styled text, convert it. 693 try { 694 CharSequence newText = Html.fromHtml(text); 695 return newText != null ? newText : text; 696 } catch (RuntimeException e) { 697 return text; 698 } 699 } else { 700 // We loaded HTML formatted text and that is what 701 // the caller wants, just return it. 702 return text.toString(); 703 } 704 } 705 if (styled) { 706 // We loaded plain text and the caller wants styled 707 // text, that is all we have so return it. 708 return text; 709 } else { 710 // We loaded plain text and the caller wants HTML 711 // text, escape it for HTML. 712 return Html.escapeHtml(text); 713 } 714 715 } catch (SecurityException e) { 716 Log.w(TAG, "Failure opening stream", e); 717 718 } catch (FileNotFoundException e) { 719 // Unable to open content URI as text... not really an 720 // error, just something to ignore. 721 722 } catch (IOException e) { 723 // Something bad has happened. 724 Log.w(TAG, "Failure loading text", e); 725 return Html.escapeHtml(e.toString()); 726 727 } finally { 728 if (stream != null) { 729 try { 730 stream.close(); 731 } catch (IOException e) { 732 } 733 } 734 } 735 } 736 737 // If we couldn't open the URI as a stream, use the URI itself as a textual 738 // representation (but not for "content", "android.resource" or "file" schemes). 739 final String scheme = mUri.getScheme(); 740 if (SCHEME_CONTENT.equals(scheme) 741 || SCHEME_ANDROID_RESOURCE.equals(scheme) 742 || SCHEME_FILE.equals(scheme)) { 743 return ""; 744 } 745 746 if (styled) { 747 return uriToStyledText(mUri.toString()); 748 } else { 749 return uriToHtml(mUri.toString()); 750 } 751 } 752 753 // Finally, if all we have is an Intent, then we can just turn that 754 // into text. Not the most user-friendly thing, but it's something. 755 if (mIntent != null) { 756 if (styled) { 757 return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 758 } else { 759 return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 760 } 761 } 762 763 // Shouldn't get here, but just in case... 764 return ""; 765 } 766 uriToHtml(String uri)767 private String uriToHtml(String uri) { 768 StringBuilder builder = new StringBuilder(256); 769 builder.append("<a href=\""); 770 builder.append(Html.escapeHtml(uri)); 771 builder.append("\">"); 772 builder.append(Html.escapeHtml(uri)); 773 builder.append("</a>"); 774 return builder.toString(); 775 } 776 uriToStyledText(String uri)777 private CharSequence uriToStyledText(String uri) { 778 SpannableStringBuilder builder = new SpannableStringBuilder(); 779 builder.append(uri); 780 builder.setSpan(new URLSpan(uri), 0, builder.length(), 781 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 782 return builder; 783 } 784 785 @Override toString()786 public String toString() { 787 StringBuilder b = new StringBuilder(128); 788 789 b.append("ClipData.Item { "); 790 toShortString(b, true); 791 b.append(" }"); 792 793 return b.toString(); 794 } 795 796 /** 797 * Appends this item to the given builder. 798 * @param redactContent If true, redacts common forms of PII; otherwise appends full 799 * details. 800 * @hide 801 */ toShortString(StringBuilder b, boolean redactContent)802 public void toShortString(StringBuilder b, boolean redactContent) { 803 boolean first = true; 804 if (mHtmlText != null) { 805 first = false; 806 if (redactContent) { 807 b.append("H(").append(mHtmlText.length()).append(')'); 808 } else { 809 b.append("H:").append(mHtmlText); 810 } 811 } 812 if (mText != null) { 813 if (!first) { 814 b.append(' '); 815 } 816 first = false; 817 if (redactContent) { 818 b.append("T(").append(mText.length()).append(')'); 819 } else { 820 b.append("T:").append(mText); 821 } 822 } 823 if (mUri != null) { 824 if (!first) { 825 b.append(' '); 826 } 827 first = false; 828 if (redactContent) { 829 b.append("U(").append(mUri.getScheme()).append(')'); 830 } else { 831 b.append("U:").append(mUri); 832 } 833 } 834 if (mIntent != null) { 835 if (!first) { 836 b.append(' '); 837 } 838 first = false; 839 b.append("I:"); 840 mIntent.toShortString(b, redactContent, true, true, true); 841 } 842 } 843 844 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)845 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 846 final long token = proto.start(fieldId); 847 848 if (mHtmlText != null) { 849 proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText); 850 } else if (mText != null) { 851 proto.write(ClipDataProto.Item.TEXT, mText.toString()); 852 } else if (mUri != null) { 853 proto.write(ClipDataProto.Item.URI, mUri.toString()); 854 } else if (mIntent != null) { 855 mIntent.dumpDebug(proto, ClipDataProto.Item.INTENT, true, true, true, true); 856 } else { 857 proto.write(ClipDataProto.Item.NOTHING, true); 858 } 859 860 proto.end(token); 861 } 862 } 863 864 /** 865 * Create a new clip. 866 * 867 * @param label Label to show to the user describing this clip. 868 * @param mimeTypes An array of MIME types this data is available as. 869 * @param item The contents of the first item in the clip. 870 */ ClipData(CharSequence label, String[] mimeTypes, Item item)871 public ClipData(CharSequence label, String[] mimeTypes, Item item) { 872 mClipDescription = new ClipDescription(label, mimeTypes); 873 if (item == null) { 874 throw new NullPointerException("item is null"); 875 } 876 mIcon = null; 877 mItems = new ArrayList<>(); 878 mItems.add(item); 879 mClipDescription.setIsStyledText(isStyledText()); 880 } 881 882 /** 883 * Create a new clip. 884 * 885 * @param description The ClipDescription describing the clip contents. 886 * @param item The contents of the first item in the clip. 887 */ ClipData(ClipDescription description, Item item)888 public ClipData(ClipDescription description, Item item) { 889 mClipDescription = description; 890 if (item == null) { 891 throw new NullPointerException("item is null"); 892 } 893 mIcon = null; 894 mItems = new ArrayList<>(); 895 mItems.add(item); 896 mClipDescription.setIsStyledText(isStyledText()); 897 } 898 899 /** 900 * Create a new clip. 901 * 902 * @param description The ClipDescription describing the clip contents. 903 * @param items The items in the clip. Not that a defensive copy of this 904 * list is not made, so caution should be taken to ensure the 905 * list is not available for further modification. 906 * @hide 907 */ ClipData(ClipDescription description, ArrayList<Item> items)908 public ClipData(ClipDescription description, ArrayList<Item> items) { 909 mClipDescription = description; 910 if (items == null) { 911 throw new NullPointerException("item is null"); 912 } 913 mIcon = null; 914 mItems = items; 915 } 916 917 /** 918 * Create a new clip that is a copy of another clip. This does a deep-copy 919 * of all items in the clip. 920 * 921 * @param other The existing ClipData that is to be copied. 922 */ ClipData(ClipData other)923 public ClipData(ClipData other) { 924 mClipDescription = other.mClipDescription; 925 mIcon = other.mIcon; 926 mItems = new ArrayList<>(other.mItems); 927 } 928 929 /** 930 * Returns a copy of the ClipData which will parcel the Item's activity infos. 931 * @hide 932 */ copyForTransferWithActivityInfo()933 public ClipData copyForTransferWithActivityInfo() { 934 ClipData copy = new ClipData(this); 935 copy.mParcelItemActivityInfos = true; 936 return copy; 937 } 938 939 /** 940 * Returns whether this clip data will parcel the Item's activity infos. 941 * @hide 942 */ willParcelWithActivityInfo()943 public boolean willParcelWithActivityInfo() { 944 return mParcelItemActivityInfos; 945 } 946 947 /** 948 * Create a new ClipData holding data of the type 949 * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. 950 * 951 * @param label User-visible label for the clip data. 952 * @param text The actual text in the clip. 953 * @return Returns a new ClipData containing the specified data. 954 */ newPlainText(CharSequence label, CharSequence text)955 static public ClipData newPlainText(CharSequence label, CharSequence text) { 956 Item item = new Item(text); 957 return new ClipData(label, MIMETYPES_TEXT_PLAIN, item); 958 } 959 960 /** 961 * Create a new ClipData holding data of the type 962 * {@link ClipDescription#MIMETYPE_TEXT_HTML}. 963 * 964 * @param label User-visible label for the clip data. 965 * @param text The text of clip as plain text, for receivers that don't 966 * handle HTML. This is required. 967 * @param htmlText The actual HTML text in the clip. 968 * @return Returns a new ClipData containing the specified data. 969 */ newHtmlText(CharSequence label, CharSequence text, String htmlText)970 static public ClipData newHtmlText(CharSequence label, CharSequence text, 971 String htmlText) { 972 Item item = new Item(text, htmlText); 973 return new ClipData(label, MIMETYPES_TEXT_HTML, item); 974 } 975 976 /** 977 * Create a new ClipData holding an Intent with MIME type 978 * {@link ClipDescription#MIMETYPE_TEXT_INTENT}. 979 * 980 * @param label User-visible label for the clip data. 981 * @param intent The actual Intent in the clip. 982 * @return Returns a new ClipData containing the specified data. 983 */ newIntent(CharSequence label, Intent intent)984 static public ClipData newIntent(CharSequence label, Intent intent) { 985 Item item = new Item(intent); 986 return new ClipData(label, MIMETYPES_TEXT_INTENT, item); 987 } 988 989 /** 990 * Create a new ClipData holding a URI. If the URI is a content: URI, 991 * this will query the content provider for the MIME type of its data and 992 * use that as the MIME type. Otherwise, it will use the MIME type 993 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 994 * 995 * @param resolver ContentResolver used to get information about the URI. 996 * @param label User-visible label for the clip data. 997 * @param uri The URI in the clip. 998 * @return Returns a new ClipData containing the specified data. 999 */ newUri(ContentResolver resolver, CharSequence label, Uri uri)1000 static public ClipData newUri(ContentResolver resolver, CharSequence label, 1001 Uri uri) { 1002 Item item = new Item(uri); 1003 String[] mimeTypes = getMimeTypes(resolver, uri); 1004 return new ClipData(label, mimeTypes, item); 1005 } 1006 1007 /** 1008 * Finds all applicable MIME types for a given URI. 1009 * 1010 * @param resolver ContentResolver used to get information about the URI. 1011 * @param uri The URI. 1012 * @return Returns an array of MIME types. 1013 */ getMimeTypes(ContentResolver resolver, Uri uri)1014 private static String[] getMimeTypes(ContentResolver resolver, Uri uri) { 1015 String[] mimeTypes = null; 1016 if (SCHEME_CONTENT.equals(uri.getScheme())) { 1017 String realType = resolver.getType(uri); 1018 mimeTypes = resolver.getStreamTypes(uri, "*/*"); 1019 if (realType != null) { 1020 if (mimeTypes == null) { 1021 mimeTypes = new String[] { realType }; 1022 } else if (!ArrayUtils.contains(mimeTypes, realType)) { 1023 String[] tmp = new String[mimeTypes.length + 1]; 1024 tmp[0] = realType; 1025 System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length); 1026 mimeTypes = tmp; 1027 } 1028 } 1029 } 1030 if (mimeTypes == null) { 1031 mimeTypes = MIMETYPES_TEXT_URILIST; 1032 } 1033 return mimeTypes; 1034 } 1035 1036 /** 1037 * Create a new ClipData holding an URI with MIME type 1038 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 1039 * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing 1040 * is inferred about the URI -- if it is a content: URI holding a bitmap, 1041 * the reported type will still be uri-list. Use this with care! 1042 * 1043 * @param label User-visible label for the clip data. 1044 * @param uri The URI in the clip. 1045 * @return Returns a new ClipData containing the specified data. 1046 */ newRawUri(CharSequence label, Uri uri)1047 static public ClipData newRawUri(CharSequence label, Uri uri) { 1048 Item item = new Item(uri); 1049 return new ClipData(label, MIMETYPES_TEXT_URILIST, item); 1050 } 1051 1052 /** 1053 * Return the {@link ClipDescription} associated with this data, describing 1054 * what it contains. 1055 */ getDescription()1056 public ClipDescription getDescription() { 1057 return mClipDescription; 1058 } 1059 1060 /** 1061 * Add a new Item to the overall ClipData container. 1062 * <p> This method will <em>not</em> update the list of available MIME types in the 1063 * {@link ClipDescription}. It should be used only when adding items which do not add new 1064 * MIME types to this clip. If this is not the case, use {@link #addItem(ContentResolver, Item)} 1065 * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types. 1066 * @param item Item to be added. 1067 */ addItem(Item item)1068 public void addItem(Item item) { 1069 if (item == null) { 1070 throw new NullPointerException("item is null"); 1071 } 1072 mItems.add(item); 1073 if (mItems.size() == 1) { 1074 mClipDescription.setIsStyledText(isStyledText()); 1075 } 1076 } 1077 1078 /** 1079 * Add a new Item to the overall ClipData container. 1080 * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types 1081 * in the {@link ClipDescription}. 1082 * @param resolver ContentResolver used to get information about the URI possibly contained in 1083 * the item. 1084 * @param item Item to be added. 1085 */ addItem(ContentResolver resolver, Item item)1086 public void addItem(ContentResolver resolver, Item item) { 1087 addItem(item); 1088 1089 if (item.getHtmlText() != null) { 1090 mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML); 1091 } else if (item.getText() != null) { 1092 mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN); 1093 } 1094 1095 if (item.getIntent() != null) { 1096 mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT); 1097 } 1098 1099 if (item.getUri() != null) { 1100 mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri())); 1101 } 1102 } 1103 1104 /** @hide */ 1105 @UnsupportedAppUsage getIcon()1106 public Bitmap getIcon() { 1107 return mIcon; 1108 } 1109 1110 /** 1111 * Return the number of items in the clip data. 1112 */ getItemCount()1113 public int getItemCount() { 1114 return mItems.size(); 1115 } 1116 1117 /** 1118 * Return a single item inside of the clip data. The index can range 1119 * from 0 to {@link #getItemCount()}-1. 1120 */ getItemAt(int index)1121 public Item getItemAt(int index) { 1122 return mItems.get(index); 1123 } 1124 1125 /** @hide */ setItemAt(int index, Item item)1126 public void setItemAt(int index, Item item) { 1127 mItems.set(index, item); 1128 } 1129 1130 /** 1131 * Prepare this {@link ClipData} to leave an app process. 1132 * 1133 * @hide 1134 */ 1135 @android.ravenwood.annotation.RavenwoodKeep prepareToLeaveProcess(boolean leavingPackage)1136 public void prepareToLeaveProcess(boolean leavingPackage) { 1137 // Assume that callers are going to be granting permissions 1138 prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION); 1139 } 1140 1141 /** 1142 * Prepare this {@link ClipData} to leave an app process. 1143 * 1144 * @hide 1145 */ 1146 @android.ravenwood.annotation.RavenwoodReplace prepareToLeaveProcess(boolean leavingPackage, int intentFlags)1147 public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) { 1148 final int size = mItems.size(); 1149 for (int i = 0; i < size; i++) { 1150 final Item item = mItems.get(i); 1151 if (item.mIntent != null) { 1152 item.mIntent.prepareToLeaveProcess(leavingPackage); 1153 } 1154 if (item.mUri != null && leavingPackage) { 1155 if (StrictMode.vmFileUriExposureEnabled()) { 1156 item.mUri.checkFileUriExposed("ClipData.Item.getUri()"); 1157 } 1158 if (StrictMode.vmContentUriWithoutPermissionEnabled()) { 1159 item.mUri.checkContentUriWithoutPermission("ClipData.Item.getUri()", 1160 intentFlags); 1161 } 1162 } 1163 } 1164 } 1165 1166 /** @hide */ prepareToLeaveProcess$ravenwood(boolean leavingPackage, int intentFlags)1167 public void prepareToLeaveProcess$ravenwood(boolean leavingPackage, int intentFlags) { 1168 // No process boundaries on Ravenwood; ignored 1169 } 1170 1171 /** {@hide} */ 1172 @android.ravenwood.annotation.RavenwoodThrow prepareToEnterProcess(AttributionSource source)1173 public void prepareToEnterProcess(AttributionSource source) { 1174 final int size = mItems.size(); 1175 for (int i = 0; i < size; i++) { 1176 final Item item = mItems.get(i); 1177 if (item.mIntent != null) { 1178 // We can't recursively claim that this data is from a protected 1179 // component, since it may have been filled in by a malicious app 1180 item.mIntent.prepareToEnterProcess(false, source); 1181 } 1182 } 1183 } 1184 1185 /** @hide */ 1186 @android.ravenwood.annotation.RavenwoodThrow fixUris(int contentUserHint)1187 public void fixUris(int contentUserHint) { 1188 final int size = mItems.size(); 1189 for (int i = 0; i < size; i++) { 1190 final Item item = mItems.get(i); 1191 if (item.mIntent != null) { 1192 item.mIntent.fixUris(contentUserHint); 1193 } 1194 if (item.mUri != null) { 1195 item.mUri = maybeAddUserId(item.mUri, contentUserHint); 1196 } 1197 } 1198 } 1199 1200 /** 1201 * Only fixing the data field of the intents 1202 * @hide 1203 */ 1204 @android.ravenwood.annotation.RavenwoodThrow fixUrisLight(int contentUserHint)1205 public void fixUrisLight(int contentUserHint) { 1206 final int size = mItems.size(); 1207 for (int i = 0; i < size; i++) { 1208 final Item item = mItems.get(i); 1209 if (item.mIntent != null) { 1210 Uri data = item.mIntent.getData(); 1211 if (data != null) { 1212 item.mIntent.setData(maybeAddUserId(data, contentUserHint)); 1213 } 1214 } 1215 if (item.mUri != null) { 1216 item.mUri = maybeAddUserId(item.mUri, contentUserHint); 1217 } 1218 } 1219 } 1220 isStyledText()1221 private boolean isStyledText() { 1222 if (mItems.isEmpty()) { 1223 return false; 1224 } 1225 final CharSequence text = mItems.get(0).getText(); 1226 if (text instanceof Spanned) { 1227 Spanned spanned = (Spanned) text; 1228 if (TextUtils.hasStyleSpan(spanned)) { 1229 return true; 1230 } 1231 } 1232 return false; 1233 } 1234 1235 @Override toString()1236 public String toString() { 1237 StringBuilder b = new StringBuilder(128); 1238 1239 b.append("ClipData { "); 1240 toShortString(b, true); 1241 b.append(" }"); 1242 1243 return b.toString(); 1244 } 1245 1246 /** 1247 * Appends this clip to the given builder. 1248 * @param redactContent If true, redacts common forms of PII; otherwise appends full details. 1249 * @hide 1250 */ toShortString(StringBuilder b, boolean redactContent)1251 public void toShortString(StringBuilder b, boolean redactContent) { 1252 boolean first; 1253 if (mClipDescription != null) { 1254 first = !mClipDescription.toShortString(b, redactContent); 1255 } else { 1256 first = true; 1257 } 1258 if (mIcon != null) { 1259 if (!first) { 1260 b.append(' '); 1261 } 1262 first = false; 1263 b.append("I:"); 1264 b.append(mIcon.getWidth()); 1265 b.append('x'); 1266 b.append(mIcon.getHeight()); 1267 } 1268 if (mItems.size() != 1) { 1269 if (!first) { 1270 b.append(' '); 1271 } 1272 first = false; 1273 b.append(mItems.size()).append(" items:"); 1274 } 1275 for (int i = 0; i < mItems.size(); i++) { 1276 if (!first) { 1277 b.append(' '); 1278 } 1279 first = false; 1280 b.append('{'); 1281 mItems.get(i).toShortString(b, redactContent); 1282 b.append('}'); 1283 } 1284 } 1285 1286 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)1287 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 1288 final long token = proto.start(fieldId); 1289 1290 if (mClipDescription != null) { 1291 mClipDescription.dumpDebug(proto, ClipDataProto.DESCRIPTION); 1292 } 1293 if (mIcon != null) { 1294 final long iToken = proto.start(ClipDataProto.ICON); 1295 proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth()); 1296 proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight()); 1297 proto.end(iToken); 1298 } 1299 for (int i = 0; i < mItems.size(); i++) { 1300 mItems.get(i).dumpDebug(proto, ClipDataProto.ITEMS); 1301 } 1302 1303 proto.end(token); 1304 } 1305 1306 /** @hide */ collectUris(List<Uri> out)1307 public void collectUris(List<Uri> out) { 1308 for (int i = 0; i < mItems.size(); ++i) { 1309 ClipData.Item item = getItemAt(i); 1310 1311 if (item.getUri() != null) { 1312 out.add(item.getUri()); 1313 } 1314 1315 Intent intent = item.getIntent(); 1316 if (intent != null) { 1317 if (intent.getData() != null) { 1318 out.add(intent.getData()); 1319 } 1320 if (intent.getClipData() != null) { 1321 intent.getClipData().collectUris(out); 1322 } 1323 } 1324 } 1325 } 1326 1327 @Override describeContents()1328 public int describeContents() { 1329 return 0; 1330 } 1331 1332 @Override writeToParcel(Parcel dest, int flags)1333 public void writeToParcel(Parcel dest, int flags) { 1334 mClipDescription.writeToParcel(dest, flags); 1335 if (mIcon != null) { 1336 dest.writeInt(1); 1337 mIcon.writeToParcel(dest, flags); 1338 } else { 1339 dest.writeInt(0); 1340 } 1341 final int N = mItems.size(); 1342 dest.writeInt(N); 1343 for (int i=0; i<N; i++) { 1344 Item item = mItems.get(i); 1345 TextUtils.writeToParcel(item.mText, dest, flags); 1346 dest.writeString8(item.mHtmlText); 1347 dest.writeTypedObject(item.mIntent, flags); 1348 dest.writeTypedObject(item.mIntentSender, flags); 1349 dest.writeTypedObject(item.mUri, flags); 1350 dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags); 1351 dest.writeTypedObject(item.mTextLinks, flags); 1352 } 1353 } 1354 ClipData(Parcel in)1355 ClipData(Parcel in) { 1356 mClipDescription = new ClipDescription(in); 1357 if (in.readInt() != 0) { 1358 mIcon = Bitmap.CREATOR.createFromParcel(in); 1359 } else { 1360 mIcon = null; 1361 } 1362 mItems = new ArrayList<>(); 1363 final int N = in.readInt(); 1364 for (int i=0; i<N; i++) { 1365 CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1366 String htmlText = in.readString8(); 1367 Intent intent = in.readTypedObject(Intent.CREATOR); 1368 IntentSender intentSender = in.readTypedObject(IntentSender.CREATOR); 1369 Uri uri = in.readTypedObject(Uri.CREATOR); 1370 ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR); 1371 TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR); 1372 Item item = new Item(text, htmlText, intent, intentSender, uri); 1373 item.setActivityInfo(info); 1374 item.setTextLinks(textLinks); 1375 mItems.add(item); 1376 } 1377 } 1378 1379 public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR = 1380 new Parcelable.Creator<>() { 1381 1382 @Override 1383 public ClipData createFromParcel(Parcel source) { 1384 return new ClipData(source); 1385 } 1386 1387 @Override 1388 public ClipData[] newArray(int size) { 1389 return new ClipData[size]; 1390 } 1391 }; 1392 } 1393