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