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 android.content.res.AssetFileDescriptor; 20 import android.graphics.Bitmap; 21 import android.net.Uri; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.text.Html; 25 import android.text.Spannable; 26 import android.text.SpannableStringBuilder; 27 import android.text.Spanned; 28 import android.text.TextUtils; 29 import android.text.style.URLSpan; 30 import android.util.Log; 31 32 import java.io.FileInputStream; 33 import java.io.FileNotFoundException; 34 import java.io.IOException; 35 import java.io.InputStreamReader; 36 import java.util.ArrayList; 37 38 /** 39 * Representation of a clipped data on the clipboard. 40 * 41 * <p>ClippedData is a complex type containing one or Item instances, 42 * each of which can hold one or more representations of an item of data. 43 * For display to the user, it also has a label and iconic representation.</p> 44 * 45 * <p>A ClipData contains a {@link ClipDescription}, which describes 46 * important meta-data about the clip. In particular, its 47 * {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)} 48 * must return correct MIME type(s) describing the data in the clip. For help 49 * in correctly constructing a clip with the correct MIME type, use 50 * {@link #newPlainText(CharSequence, CharSequence)}, 51 * {@link #newUri(ContentResolver, CharSequence, Uri)}, and 52 * {@link #newIntent(CharSequence, Intent)}. 53 * 54 * <p>Each Item instance can be one of three main classes of data: a simple 55 * CharSequence of text, a single Intent object, or a Uri. See {@link Item} 56 * for more details. 57 * 58 * <div class="special reference"> 59 * <h3>Developer Guides</h3> 60 * <p>For more information about using the clipboard framework, read the 61 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a> 62 * developer guide.</p> 63 * </div> 64 * 65 * <a name="ImplementingPaste"></a> 66 * <h3>Implementing Paste or Drop</h3> 67 * 68 * <p>To implement a paste or drop of a ClippedData object into an application, 69 * the application must correctly interpret the data for its use. If the {@link Item} 70 * it contains is simple text or an Intent, there is little to be done: text 71 * can only be interpreted as text, and an Intent will typically be used for 72 * creating shortcuts (such as placing icons on the home screen) or other 73 * actions. 74 * 75 * <p>If all you want is the textual representation of the clipped data, you 76 * can use the convenience method {@link Item#coerceToText Item.coerceToText}. 77 * In this case there is generally no need to worry about the MIME types 78 * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}, 79 * since any clip item an always be converted to a string. 80 * 81 * <p>More complicated exchanges will be done through URIs, in particular 82 * "content:" URIs. A content URI allows the recipient of a ClippedData item 83 * to interact closely with the ContentProvider holding the data in order to 84 * negotiate the transfer of that data. The clip must also be filled in with 85 * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Uri)} 86 * will take care of correctly doing this. 87 * 88 * <p>For example, here is the paste function of a simple NotePad application. 89 * When retrieving the data from the clipboard, it can do either two things: 90 * if the clipboard contains a URI reference to an existing note, it copies 91 * the entire structure of the note into a new note; otherwise, it simply 92 * coerces the clip into text and uses that as the new note's contents. 93 * 94 * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java 95 * paste} 96 * 97 * <p>In many cases an application can paste various types of streams of data. For 98 * example, an e-mail application may want to allow the user to paste an image 99 * or other binary data as an attachment. This is accomplished through the 100 * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and 101 * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)} 102 * methods. These allow a client to discover the type(s) of data that a particular 103 * content URI can make available as a stream and retrieve the stream of data. 104 * 105 * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText} 106 * itself uses this to try to retrieve a URI clip as a stream of text: 107 * 108 * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText} 109 * 110 * <a name="ImplementingCopy"></a> 111 * <h3>Implementing Copy or Drag</h3> 112 * 113 * <p>To be the source of a clip, the application must construct a ClippedData 114 * object that any recipient can interpret best for their context. If the clip 115 * is to contain a simple text, Intent, or URI, this is easy: an {@link Item} 116 * containing the appropriate data type can be constructed and used. 117 * 118 * <p>More complicated data types require the implementation of support in 119 * a ContentProvider for describing and generating the data for the recipient. 120 * A common scenario is one where an application places on the clipboard the 121 * content: URI of an object that the user has copied, with the data at that 122 * URI consisting of a complicated structure that only other applications with 123 * direct knowledge of the structure can use. 124 * 125 * <p>For applications that do not have intrinsic knowledge of the data structure, 126 * the content provider holding it can make the data available as an arbitrary 127 * number of types of data streams. This is done by implementing the 128 * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and 129 * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)} 130 * methods. 131 * 132 * <p>Going back to our simple NotePad application, this is the implementation 133 * it may have to convert a single note URI (consisting of a title and the note 134 * text) into a stream of plain text data. 135 * 136 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java 137 * stream} 138 * 139 * <p>The copy operation in our NotePad application is now just a simple matter 140 * of making a clip containing the URI of the note being copied: 141 * 142 * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java 143 * copy} 144 * 145 * <p>Note if a paste operation needs this clip as text (for example to paste 146 * into an editor), then {@link Item#coerceToText(Context)} will ask the content 147 * provider for the clip URI as text and successfully paste the entire note. 148 */ 149 public class ClipData implements Parcelable { 150 static final String[] MIMETYPES_TEXT_PLAIN = new String[] { 151 ClipDescription.MIMETYPE_TEXT_PLAIN }; 152 static final String[] MIMETYPES_TEXT_HTML = new String[] { 153 ClipDescription.MIMETYPE_TEXT_HTML }; 154 static final String[] MIMETYPES_TEXT_URILIST = new String[] { 155 ClipDescription.MIMETYPE_TEXT_URILIST }; 156 static final String[] MIMETYPES_TEXT_INTENT = new String[] { 157 ClipDescription.MIMETYPE_TEXT_INTENT }; 158 159 final ClipDescription mClipDescription; 160 161 final Bitmap mIcon; 162 163 final ArrayList<Item> mItems; 164 165 /** 166 * Description of a single item in a ClippedData. 167 * 168 * <p>The types than an individual item can currently contain are:</p> 169 * 170 * <ul> 171 * <li> Text: a basic string of text. This is actually a CharSequence, 172 * so it can be formatted text supported by corresponding Android built-in 173 * style spans. (Custom application spans are not supported and will be 174 * stripped when transporting through the clipboard.) 175 * <li> Intent: an arbitrary Intent object. A typical use is the shortcut 176 * to create when pasting a clipped item on to the home screen. 177 * <li> Uri: a URI reference. This may be any URI (such as an http: URI 178 * representing a bookmark), however it is often a content: URI. Using 179 * content provider references as clips like this allows an application to 180 * share complex or large clips through the standard content provider 181 * facilities. 182 * </ul> 183 */ 184 public static class Item { 185 final CharSequence mText; 186 final String mHtmlText; 187 final Intent mIntent; 188 final Uri mUri; 189 190 /** 191 * Create an Item consisting of a single block of (possibly styled) text. 192 */ Item(CharSequence text)193 public Item(CharSequence text) { 194 mText = text; 195 mHtmlText = null; 196 mIntent = null; 197 mUri = null; 198 } 199 200 /** 201 * Create an Item consisting of a single block of (possibly styled) text, 202 * with an alternative HTML formatted representation. You <em>must</em> 203 * supply a plain text representation in addition to HTML text; coercion 204 * will not be done from HTML formated text into plain text. 205 */ Item(CharSequence text, String htmlText)206 public Item(CharSequence text, String htmlText) { 207 mText = text; 208 mHtmlText = htmlText; 209 mIntent = null; 210 mUri = null; 211 } 212 213 /** 214 * Create an Item consisting of an arbitrary Intent. 215 */ Item(Intent intent)216 public Item(Intent intent) { 217 mText = null; 218 mHtmlText = null; 219 mIntent = intent; 220 mUri = null; 221 } 222 223 /** 224 * Create an Item consisting of an arbitrary URI. 225 */ Item(Uri uri)226 public Item(Uri uri) { 227 mText = null; 228 mHtmlText = null; 229 mIntent = null; 230 mUri = uri; 231 } 232 233 /** 234 * Create a complex Item, containing multiple representations of 235 * text, Intent, and/or URI. 236 */ Item(CharSequence text, Intent intent, Uri uri)237 public Item(CharSequence text, Intent intent, Uri uri) { 238 mText = text; 239 mHtmlText = null; 240 mIntent = intent; 241 mUri = uri; 242 } 243 244 /** 245 * Create a complex Item, containing multiple representations of 246 * text, HTML text, Intent, and/or URI. If providing HTML text, you 247 * <em>must</em> supply a plain text representation as well; coercion 248 * will not be done from HTML formated text into plain text. 249 */ Item(CharSequence text, String htmlText, Intent intent, Uri uri)250 public Item(CharSequence text, String htmlText, Intent intent, Uri uri) { 251 if (htmlText != null && text == null) { 252 throw new IllegalArgumentException( 253 "Plain text must be supplied if HTML text is supplied"); 254 } 255 mText = text; 256 mHtmlText = htmlText; 257 mIntent = intent; 258 mUri = uri; 259 } 260 261 /** 262 * Retrieve the raw text contained in this Item. 263 */ getText()264 public CharSequence getText() { 265 return mText; 266 } 267 268 /** 269 * Retrieve the raw HTML text contained in this Item. 270 */ getHtmlText()271 public String getHtmlText() { 272 return mHtmlText; 273 } 274 275 /** 276 * Retrieve the raw Intent contained in this Item. 277 */ getIntent()278 public Intent getIntent() { 279 return mIntent; 280 } 281 282 /** 283 * Retrieve the raw URI contained in this Item. 284 */ getUri()285 public Uri getUri() { 286 return mUri; 287 } 288 289 /** 290 * Turn this item into text, regardless of the type of data it 291 * actually contains. 292 * 293 * <p>The algorithm for deciding what text to return is: 294 * <ul> 295 * <li> If {@link #getText} is non-null, return that. 296 * <li> If {@link #getUri} is non-null, try to retrieve its data 297 * as a text stream from its content provider. If this succeeds, copy 298 * the text into a String and return it. If it is not a content: URI or 299 * the content provider does not supply a text representation, return 300 * the raw URI as a string. 301 * <li> If {@link #getIntent} is non-null, convert that to an intent: 302 * URI and return it. 303 * <li> Otherwise, return an empty string. 304 * </ul> 305 * 306 * @param context The caller's Context, from which its ContentResolver 307 * and other things can be retrieved. 308 * @return Returns the item's textual representation. 309 */ 310 //BEGIN_INCLUDE(coerceToText) coerceToText(Context context)311 public CharSequence coerceToText(Context context) { 312 // If this Item has an explicit textual value, simply return that. 313 CharSequence text = getText(); 314 if (text != null) { 315 return text; 316 } 317 318 // If this Item has a URI value, try using that. 319 Uri uri = getUri(); 320 if (uri != null) { 321 322 // First see if the URI can be opened as a plain text stream 323 // (of any sub-type). If so, this is the best textual 324 // representation for it. 325 FileInputStream stream = null; 326 try { 327 // Ask for a stream of the desired type. 328 AssetFileDescriptor descr = context.getContentResolver() 329 .openTypedAssetFileDescriptor(uri, "text/*", null); 330 stream = descr.createInputStream(); 331 InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); 332 333 // Got it... copy the stream into a local string and return it. 334 StringBuilder builder = new StringBuilder(128); 335 char[] buffer = new char[8192]; 336 int len; 337 while ((len=reader.read(buffer)) > 0) { 338 builder.append(buffer, 0, len); 339 } 340 return builder.toString(); 341 342 } catch (FileNotFoundException e) { 343 // Unable to open content URI as text... not really an 344 // error, just something to ignore. 345 346 } catch (IOException e) { 347 // Something bad has happened. 348 Log.w("ClippedData", "Failure loading text", e); 349 return e.toString(); 350 351 } finally { 352 if (stream != null) { 353 try { 354 stream.close(); 355 } catch (IOException e) { 356 } 357 } 358 } 359 360 // If we couldn't open the URI as a stream, then the URI itself 361 // probably serves fairly well as a textual representation. 362 return uri.toString(); 363 } 364 365 // Finally, if all we have is an Intent, then we can just turn that 366 // into text. Not the most user-friendly thing, but it's something. 367 Intent intent = getIntent(); 368 if (intent != null) { 369 return intent.toUri(Intent.URI_INTENT_SCHEME); 370 } 371 372 // Shouldn't get here, but just in case... 373 return ""; 374 } 375 //END_INCLUDE(coerceToText) 376 377 /** 378 * Like {@link #coerceToHtmlText(Context)}, but any text that would 379 * be returned as HTML formatting will be returned as text with 380 * style spans. 381 * @param context The caller's Context, from which its ContentResolver 382 * and other things can be retrieved. 383 * @return Returns the item's textual representation. 384 */ coerceToStyledText(Context context)385 public CharSequence coerceToStyledText(Context context) { 386 CharSequence text = getText(); 387 if (text instanceof Spanned) { 388 return text; 389 } 390 String htmlText = getHtmlText(); 391 if (htmlText != null) { 392 try { 393 CharSequence newText = Html.fromHtml(htmlText); 394 if (newText != null) { 395 return newText; 396 } 397 } catch (RuntimeException e) { 398 // If anything bad happens, we'll fall back on the plain text. 399 } 400 } 401 402 if (text != null) { 403 return text; 404 } 405 return coerceToHtmlOrStyledText(context, true); 406 } 407 408 /** 409 * Turn this item into HTML text, regardless of the type of data it 410 * actually contains. 411 * 412 * <p>The algorithm for deciding what text to return is: 413 * <ul> 414 * <li> If {@link #getHtmlText} is non-null, return that. 415 * <li> If {@link #getText} is non-null, return that, converting to 416 * valid HTML text. If this text contains style spans, 417 * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to 418 * convert them to HTML formatting. 419 * <li> If {@link #getUri} is non-null, try to retrieve its data 420 * as a text stream from its content provider. If the provider can 421 * supply text/html data, that will be preferred and returned as-is. 422 * Otherwise, any text/* data will be returned and escaped to HTML. 423 * If it is not a content: URI or the content provider does not supply 424 * a text representation, HTML text containing a link to the URI 425 * will be returned. 426 * <li> If {@link #getIntent} is non-null, convert that to an intent: 427 * URI and return as an HTML link. 428 * <li> Otherwise, return an empty string. 429 * </ul> 430 * 431 * @param context The caller's Context, from which its ContentResolver 432 * and other things can be retrieved. 433 * @return Returns the item's representation as HTML text. 434 */ coerceToHtmlText(Context context)435 public String coerceToHtmlText(Context context) { 436 // If the item has an explicit HTML value, simply return that. 437 String htmlText = getHtmlText(); 438 if (htmlText != null) { 439 return htmlText; 440 } 441 442 // If this Item has a plain text value, return it as HTML. 443 CharSequence text = getText(); 444 if (text != null) { 445 if (text instanceof Spanned) { 446 return Html.toHtml((Spanned)text); 447 } 448 return Html.escapeHtml(text); 449 } 450 451 text = coerceToHtmlOrStyledText(context, false); 452 return text != null ? text.toString() : null; 453 } 454 coerceToHtmlOrStyledText(Context context, boolean styled)455 private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) { 456 // If this Item has a URI value, try using that. 457 if (mUri != null) { 458 459 // Check to see what data representations the content 460 // provider supports. We would like HTML text, but if that 461 // is not possible we'll live with plan text. 462 String[] types = context.getContentResolver().getStreamTypes(mUri, "text/*"); 463 boolean hasHtml = false; 464 boolean hasText = false; 465 if (types != null) { 466 for (String type : types) { 467 if ("text/html".equals(type)) { 468 hasHtml = true; 469 } else if (type.startsWith("text/")) { 470 hasText = true; 471 } 472 } 473 } 474 475 // If the provider can serve data we can use, open and load it. 476 if (hasHtml || hasText) { 477 FileInputStream stream = null; 478 try { 479 // Ask for a stream of the desired type. 480 AssetFileDescriptor descr = context.getContentResolver() 481 .openTypedAssetFileDescriptor(mUri, 482 hasHtml ? "text/html" : "text/plain", null); 483 stream = descr.createInputStream(); 484 InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); 485 486 // Got it... copy the stream into a local string and return it. 487 StringBuilder builder = new StringBuilder(128); 488 char[] buffer = new char[8192]; 489 int len; 490 while ((len=reader.read(buffer)) > 0) { 491 builder.append(buffer, 0, len); 492 } 493 String text = builder.toString(); 494 if (hasHtml) { 495 if (styled) { 496 // We loaded HTML formatted text and the caller 497 // want styled text, convert it. 498 try { 499 CharSequence newText = Html.fromHtml(text); 500 return newText != null ? newText : text; 501 } catch (RuntimeException e) { 502 return text; 503 } 504 } else { 505 // We loaded HTML formatted text and that is what 506 // the caller wants, just return it. 507 return text.toString(); 508 } 509 } 510 if (styled) { 511 // We loaded plain text and the caller wants styled 512 // text, that is all we have so return it. 513 return text; 514 } else { 515 // We loaded plain text and the caller wants HTML 516 // text, escape it for HTML. 517 return Html.escapeHtml(text); 518 } 519 520 } catch (FileNotFoundException e) { 521 // Unable to open content URI as text... not really an 522 // error, just something to ignore. 523 524 } catch (IOException e) { 525 // Something bad has happened. 526 Log.w("ClippedData", "Failure loading text", e); 527 return Html.escapeHtml(e.toString()); 528 529 } finally { 530 if (stream != null) { 531 try { 532 stream.close(); 533 } catch (IOException e) { 534 } 535 } 536 } 537 } 538 539 // If we couldn't open the URI as a stream, then we can build 540 // some HTML text with the URI itself. 541 // probably serves fairly well as a textual representation. 542 if (styled) { 543 return uriToStyledText(mUri.toString()); 544 } else { 545 return uriToHtml(mUri.toString()); 546 } 547 } 548 549 // Finally, if all we have is an Intent, then we can just turn that 550 // into text. Not the most user-friendly thing, but it's something. 551 if (mIntent != null) { 552 if (styled) { 553 return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 554 } else { 555 return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME)); 556 } 557 } 558 559 // Shouldn't get here, but just in case... 560 return ""; 561 } 562 uriToHtml(String uri)563 private String uriToHtml(String uri) { 564 StringBuilder builder = new StringBuilder(256); 565 builder.append("<a href=\""); 566 builder.append(Html.escapeHtml(uri)); 567 builder.append("\">"); 568 builder.append(Html.escapeHtml(uri)); 569 builder.append("</a>"); 570 return builder.toString(); 571 } 572 uriToStyledText(String uri)573 private CharSequence uriToStyledText(String uri) { 574 SpannableStringBuilder builder = new SpannableStringBuilder(); 575 builder.append(uri); 576 builder.setSpan(new URLSpan(uri), 0, builder.length(), 577 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 578 return builder; 579 } 580 581 @Override toString()582 public String toString() { 583 StringBuilder b = new StringBuilder(128); 584 585 b.append("ClipData.Item { "); 586 toShortString(b); 587 b.append(" }"); 588 589 return b.toString(); 590 } 591 592 /** @hide */ toShortString(StringBuilder b)593 public void toShortString(StringBuilder b) { 594 if (mHtmlText != null) { 595 b.append("H:"); 596 b.append(mHtmlText); 597 } else if (mText != null) { 598 b.append("T:"); 599 b.append(mText); 600 } else if (mUri != null) { 601 b.append("U:"); 602 b.append(mUri); 603 } else if (mIntent != null) { 604 b.append("I:"); 605 mIntent.toShortString(b, true, true, true, true); 606 } else { 607 b.append("NULL"); 608 } 609 } 610 } 611 612 /** 613 * Create a new clip. 614 * 615 * @param label Label to show to the user describing this clip. 616 * @param mimeTypes An array of MIME types this data is available as. 617 * @param item The contents of the first item in the clip. 618 */ ClipData(CharSequence label, String[] mimeTypes, Item item)619 public ClipData(CharSequence label, String[] mimeTypes, Item item) { 620 mClipDescription = new ClipDescription(label, mimeTypes); 621 if (item == null) { 622 throw new NullPointerException("item is null"); 623 } 624 mIcon = null; 625 mItems = new ArrayList<Item>(); 626 mItems.add(item); 627 } 628 629 /** 630 * Create a new clip. 631 * 632 * @param description The ClipDescription describing the clip contents. 633 * @param item The contents of the first item in the clip. 634 */ ClipData(ClipDescription description, Item item)635 public ClipData(ClipDescription description, Item item) { 636 mClipDescription = description; 637 if (item == null) { 638 throw new NullPointerException("item is null"); 639 } 640 mIcon = null; 641 mItems = new ArrayList<Item>(); 642 mItems.add(item); 643 } 644 645 /** 646 * Create a new clip that is a copy of another clip. This does a deep-copy 647 * of all items in the clip. 648 * 649 * @param other The existing ClipData that is to be copied. 650 */ ClipData(ClipData other)651 public ClipData(ClipData other) { 652 mClipDescription = other.mClipDescription; 653 mIcon = other.mIcon; 654 mItems = new ArrayList<Item>(other.mItems); 655 } 656 657 /** 658 * Create a new ClipData holding data of the type 659 * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. 660 * 661 * @param label User-visible label for the clip data. 662 * @param text The actual text in the clip. 663 * @return Returns a new ClipData containing the specified data. 664 */ newPlainText(CharSequence label, CharSequence text)665 static public ClipData newPlainText(CharSequence label, CharSequence text) { 666 Item item = new Item(text); 667 return new ClipData(label, MIMETYPES_TEXT_PLAIN, item); 668 } 669 670 /** 671 * Create a new ClipData holding data of the type 672 * {@link ClipDescription#MIMETYPE_TEXT_HTML}. 673 * 674 * @param label User-visible label for the clip data. 675 * @param text The text of clip as plain text, for receivers that don't 676 * handle HTML. This is required. 677 * @param htmlText The actual HTML text in the clip. 678 * @return Returns a new ClipData containing the specified data. 679 */ newHtmlText(CharSequence label, CharSequence text, String htmlText)680 static public ClipData newHtmlText(CharSequence label, CharSequence text, 681 String htmlText) { 682 Item item = new Item(text, htmlText); 683 return new ClipData(label, MIMETYPES_TEXT_HTML, item); 684 } 685 686 /** 687 * Create a new ClipData holding an Intent with MIME type 688 * {@link ClipDescription#MIMETYPE_TEXT_INTENT}. 689 * 690 * @param label User-visible label for the clip data. 691 * @param intent The actual Intent in the clip. 692 * @return Returns a new ClipData containing the specified data. 693 */ newIntent(CharSequence label, Intent intent)694 static public ClipData newIntent(CharSequence label, Intent intent) { 695 Item item = new Item(intent); 696 return new ClipData(label, MIMETYPES_TEXT_INTENT, item); 697 } 698 699 /** 700 * Create a new ClipData holding a URI. If the URI is a content: URI, 701 * this will query the content provider for the MIME type of its data and 702 * use that as the MIME type. Otherwise, it will use the MIME type 703 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 704 * 705 * @param resolver ContentResolver used to get information about the URI. 706 * @param label User-visible label for the clip data. 707 * @param uri The URI in the clip. 708 * @return Returns a new ClipData containing the specified data. 709 */ newUri(ContentResolver resolver, CharSequence label, Uri uri)710 static public ClipData newUri(ContentResolver resolver, CharSequence label, 711 Uri uri) { 712 Item item = new Item(uri); 713 String[] mimeTypes = null; 714 if ("content".equals(uri.getScheme())) { 715 String realType = resolver.getType(uri); 716 mimeTypes = resolver.getStreamTypes(uri, "*/*"); 717 if (mimeTypes == null) { 718 if (realType != null) { 719 mimeTypes = new String[] { realType, ClipDescription.MIMETYPE_TEXT_URILIST }; 720 } 721 } else { 722 String[] tmp = new String[mimeTypes.length + (realType != null ? 2 : 1)]; 723 int i = 0; 724 if (realType != null) { 725 tmp[0] = realType; 726 i++; 727 } 728 System.arraycopy(mimeTypes, 0, tmp, i, mimeTypes.length); 729 tmp[i + mimeTypes.length] = ClipDescription.MIMETYPE_TEXT_URILIST; 730 mimeTypes = tmp; 731 } 732 } 733 if (mimeTypes == null) { 734 mimeTypes = MIMETYPES_TEXT_URILIST; 735 } 736 return new ClipData(label, mimeTypes, item); 737 } 738 739 /** 740 * Create a new ClipData holding an URI with MIME type 741 * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. 742 * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing 743 * is inferred about the URI -- if it is a content: URI holding a bitmap, 744 * the reported type will still be uri-list. Use this with care! 745 * 746 * @param label User-visible label for the clip data. 747 * @param uri The URI in the clip. 748 * @return Returns a new ClipData containing the specified data. 749 */ newRawUri(CharSequence label, Uri uri)750 static public ClipData newRawUri(CharSequence label, Uri uri) { 751 Item item = new Item(uri); 752 return new ClipData(label, MIMETYPES_TEXT_URILIST, item); 753 } 754 755 /** 756 * Return the {@link ClipDescription} associated with this data, describing 757 * what it contains. 758 */ getDescription()759 public ClipDescription getDescription() { 760 return mClipDescription; 761 } 762 763 /** 764 * Add a new Item to the overall ClipData container. 765 */ addItem(Item item)766 public void addItem(Item item) { 767 if (item == null) { 768 throw new NullPointerException("item is null"); 769 } 770 mItems.add(item); 771 } 772 773 /** @hide */ getIcon()774 public Bitmap getIcon() { 775 return mIcon; 776 } 777 778 /** 779 * Return the number of items in the clip data. 780 */ getItemCount()781 public int getItemCount() { 782 return mItems.size(); 783 } 784 785 /** 786 * Return a single item inside of the clip data. The index can range 787 * from 0 to {@link #getItemCount()}-1. 788 */ getItemAt(int index)789 public Item getItemAt(int index) { 790 return mItems.get(index); 791 } 792 793 @Override toString()794 public String toString() { 795 StringBuilder b = new StringBuilder(128); 796 797 b.append("ClipData { "); 798 toShortString(b); 799 b.append(" }"); 800 801 return b.toString(); 802 } 803 804 /** @hide */ toShortString(StringBuilder b)805 public void toShortString(StringBuilder b) { 806 boolean first; 807 if (mClipDescription != null) { 808 first = !mClipDescription.toShortString(b); 809 } else { 810 first = true; 811 } 812 if (mIcon != null) { 813 if (!first) { 814 b.append(' '); 815 } 816 first = false; 817 b.append("I:"); 818 b.append(mIcon.getWidth()); 819 b.append('x'); 820 b.append(mIcon.getHeight()); 821 } 822 for (int i=0; i<mItems.size(); i++) { 823 if (!first) { 824 b.append(' '); 825 } 826 first = false; 827 b.append('{'); 828 mItems.get(i).toShortString(b); 829 b.append('}'); 830 } 831 } 832 833 @Override describeContents()834 public int describeContents() { 835 return 0; 836 } 837 838 @Override writeToParcel(Parcel dest, int flags)839 public void writeToParcel(Parcel dest, int flags) { 840 mClipDescription.writeToParcel(dest, flags); 841 if (mIcon != null) { 842 dest.writeInt(1); 843 mIcon.writeToParcel(dest, flags); 844 } else { 845 dest.writeInt(0); 846 } 847 final int N = mItems.size(); 848 dest.writeInt(N); 849 for (int i=0; i<N; i++) { 850 Item item = mItems.get(i); 851 TextUtils.writeToParcel(item.mText, dest, flags); 852 dest.writeString(item.mHtmlText); 853 if (item.mIntent != null) { 854 dest.writeInt(1); 855 item.mIntent.writeToParcel(dest, flags); 856 } else { 857 dest.writeInt(0); 858 } 859 if (item.mUri != null) { 860 dest.writeInt(1); 861 item.mUri.writeToParcel(dest, flags); 862 } else { 863 dest.writeInt(0); 864 } 865 } 866 } 867 ClipData(Parcel in)868 ClipData(Parcel in) { 869 mClipDescription = new ClipDescription(in); 870 if (in.readInt() != 0) { 871 mIcon = Bitmap.CREATOR.createFromParcel(in); 872 } else { 873 mIcon = null; 874 } 875 mItems = new ArrayList<Item>(); 876 final int N = in.readInt(); 877 for (int i=0; i<N; i++) { 878 CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 879 String htmlText = in.readString(); 880 Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null; 881 Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null; 882 mItems.add(new Item(text, htmlText, intent, uri)); 883 } 884 } 885 886 public static final Parcelable.Creator<ClipData> CREATOR = 887 new Parcelable.Creator<ClipData>() { 888 889 public ClipData createFromParcel(Parcel source) { 890 return new ClipData(source); 891 } 892 893 public ClipData[] newArray(int size) { 894 return new ClipData[size]; 895 } 896 }; 897 } 898