1/* 2 * Copyright (C) 2011 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 17package android.support.v4.app; 18 19import android.app.Activity; 20import android.content.ComponentName; 21import android.content.Intent; 22import android.content.pm.PackageManager; 23import android.content.pm.PackageManager.NameNotFoundException; 24import android.graphics.drawable.Drawable; 25import android.net.Uri; 26import android.os.Build; 27import android.support.v4.content.IntentCompat; 28import android.support.v4.view.MenuItemCompat; 29import android.text.Html; 30import android.text.Spanned; 31import android.util.Log; 32import android.view.Menu; 33import android.view.MenuItem; 34 35import java.util.ArrayList; 36 37/** 38 * Extra helper functionality for sharing data between activities. 39 * 40 * ShareCompat provides functionality to extend the {@link Intent#ACTION_SEND}/ 41 * {@link Intent#ACTION_SEND_MULTIPLE} protocol and support retrieving more info 42 * about the activity that invoked a social sharing action. 43 * 44 * {@link IntentBuilder} provides helper functions for constructing a sharing 45 * intent that always includes data about the calling activity and app. 46 * This lets the called activity provide attribution for the app that shared 47 * content. Constructing an intent this way can be done in a method-chaining style. 48 * To obtain an IntentBuilder with info about your calling activity, use the static 49 * method {@link IntentBuilder#from(Activity)}. 50 * 51 * {@link IntentReader} provides helper functions for parsing the defined extras 52 * within an {@link Intent#ACTION_SEND} or {@link Intent#ACTION_SEND_MULTIPLE} intent 53 * used to launch an activity. You can also obtain a Drawable for the caller's 54 * application icon and the application's localized label (the app's human-readable name). 55 * Social apps that enable sharing content are encouraged to use this information 56 * to call out the app that the content was shared from. 57 */ 58public class ShareCompat { 59 /** 60 * Intent extra that stores the name of the calling package for an ACTION_SEND intent. 61 * When an activity is started using startActivityForResult this is redundant info. 62 * (It is also provided by {@link Activity#getCallingPackage()}.) 63 * 64 * Instead of using this constant directly, consider using {@link #getCallingPackage(Activity)} 65 * or {@link IntentReader#getCallingPackage()}. 66 */ 67 public static final String EXTRA_CALLING_PACKAGE = 68 "android.support.v4.app.EXTRA_CALLING_PACKAGE"; 69 70 /** 71 * Intent extra that stores the {@link ComponentName} of the calling activity for 72 * an ACTION_SEND intent. 73 */ 74 public static final String EXTRA_CALLING_ACTIVITY = 75 "android.support.v4.app.EXTRA_CALLING_ACTIVITY"; 76 77 /** 78 * Compatibility shims for sharing operations 79 */ 80 interface ShareCompatImpl { 81 void configureMenuItem(MenuItem item, IntentBuilder shareIntent); 82 String escapeHtml(CharSequence text); 83 } 84 85 static class ShareCompatImplBase implements ShareCompatImpl { 86 public void configureMenuItem(MenuItem item, IntentBuilder shareIntent) { 87 item.setIntent(shareIntent.createChooserIntent()); 88 } 89 90 public String escapeHtml(CharSequence text) { 91 StringBuilder out = new StringBuilder(); 92 withinStyle(out, text, 0, text.length()); 93 return out.toString(); 94 } 95 96 private static void withinStyle(StringBuilder out, CharSequence text, 97 int start, int end) { 98 for (int i = start; i < end; i++) { 99 char c = text.charAt(i); 100 101 if (c == '<') { 102 out.append("<"); 103 } else if (c == '>') { 104 out.append(">"); 105 } else if (c == '&') { 106 out.append("&"); 107 } else if (c > 0x7E || c < ' ') { 108 out.append("&#" + ((int) c) + ";"); 109 } else if (c == ' ') { 110 while (i + 1 < end && text.charAt(i + 1) == ' ') { 111 out.append(" "); 112 i++; 113 } 114 115 out.append(' '); 116 } else { 117 out.append(c); 118 } 119 } 120 } 121 } 122 123 static class ShareCompatImplICS extends ShareCompatImplBase { 124 public void configureMenuItem(MenuItem item, IntentBuilder shareIntent) { 125 ShareCompatICS.configureMenuItem(item, shareIntent.getActivity(), 126 shareIntent.getIntent()); 127 if (shouldAddChooserIntent(item)) { 128 item.setIntent(shareIntent.createChooserIntent()); 129 } 130 } 131 132 boolean shouldAddChooserIntent(MenuItem item) { 133 return !item.hasSubMenu(); 134 } 135 } 136 137 static class ShareCompatImplJB extends ShareCompatImplICS { 138 public String escapeHtml(CharSequence html) { 139 return ShareCompatJB.escapeHtml(html); 140 } 141 142 @Override 143 boolean shouldAddChooserIntent(MenuItem item) { 144 return false; 145 } 146 } 147 148 private static ShareCompatImpl IMPL; 149 150 static { 151 if (Build.VERSION.SDK_INT >= 16) { 152 IMPL = new ShareCompatImplJB(); 153 } else if (Build.VERSION.SDK_INT >= 14) { 154 IMPL = new ShareCompatImplICS(); 155 } else { 156 IMPL = new ShareCompatImplBase(); 157 } 158 } 159 160 /** 161 * Retrieve the name of the package that launched calledActivity from a share intent. 162 * Apps that provide social sharing functionality can use this to provide attribution 163 * for the app that shared the content. 164 * 165 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 166 * application. As such it should not be trusted for accuracy in the context of 167 * security or verification.</p> 168 * 169 * @param calledActivity Current activity that was launched to share content 170 * @return Name of the calling package 171 */ 172 public static String getCallingPackage(Activity calledActivity) { 173 String result = calledActivity.getCallingPackage(); 174 if (result == null) { 175 result = calledActivity.getIntent().getStringExtra(EXTRA_CALLING_PACKAGE); 176 } 177 return result; 178 } 179 180 /** 181 * Retrieve the ComponentName of the activity that launched calledActivity from a share intent. 182 * Apps that provide social sharing functionality can use this to provide attribution 183 * for the app that shared the content. 184 * 185 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 186 * application. As such it should not be trusted for accuracy in the context of 187 * security or verification.</p> 188 * 189 * @param calledActivity Current activity that was launched to share content 190 * @return ComponentName of the calling activity 191 */ 192 public static ComponentName getCallingActivity(Activity calledActivity) { 193 ComponentName result = calledActivity.getCallingActivity(); 194 if (result == null) { 195 result = calledActivity.getIntent().getParcelableExtra(EXTRA_CALLING_ACTIVITY); 196 } 197 return result; 198 } 199 200 /** 201 * Configure a {@link MenuItem} to act as a sharing action. 202 * 203 * <p>If the app is running on API level 14 or higher (Android 4.0/Ice Cream Sandwich) 204 * this method will configure a ShareActionProvider to provide a more robust UI 205 * for selecting the target of the share. History will be tracked for each calling 206 * activity in a file named with the prefix ".sharecompat_" in the application's 207 * private data directory. If the application wishes to set this MenuItem to show 208 * as an action in the Action Bar it should use 209 * {@link MenuItemCompat#setShowAsAction(MenuItem, int)} to request that behavior 210 * in addition to calling this method.</p> 211 * 212 * <p>If the app is running on an older platform version this method will configure 213 * a standard activity chooser dialog for the menu item.</p> 214 * 215 * <p>During the calling activity's lifecycle, if data within the share intent must 216 * change the app should change that state in one of several ways:</p> 217 * <ul> 218 * <li>Call {@link ActivityCompat#invalidateOptionsMenu(Activity)}. If the app is running 219 * on API level 11 or above and uses the Action Bar its menu will be recreated and rebuilt. 220 * If not, the activity will receive a call to {@link Activity#onPrepareOptionsMenu(Menu)} 221 * the next time the user presses the menu key to open the options menu panel. The activity 222 * can then call configureMenuItem again with a new or altered IntentBuilder to reconfigure 223 * the share menu item.</li> 224 * <li>Keep a reference to the MenuItem object for the share item once it has been created 225 * and call configureMenuItem to update the associated sharing intent as needed.</li> 226 * </ul> 227 * 228 * @param item MenuItem to configure for sharing 229 * @param shareIntent IntentBuilder with data about the content to share 230 */ 231 public static void configureMenuItem(MenuItem item, IntentBuilder shareIntent) { 232 IMPL.configureMenuItem(item, shareIntent); 233 } 234 235 /** 236 * Configure a menu item to act as a sharing action. 237 * 238 * @param menu Menu containing the item to use for sharing 239 * @param menuItemId ID of the share item within menu 240 * @param shareIntent IntentBuilder with data about the content to share 241 * @see #configureMenuItem(MenuItem, IntentBuilder) 242 */ 243 public static void configureMenuItem(Menu menu, int menuItemId, IntentBuilder shareIntent) { 244 MenuItem item = menu.findItem(menuItemId); 245 if (item == null) { 246 throw new IllegalArgumentException("Could not find menu item with id " + menuItemId + 247 " in the supplied menu"); 248 } 249 configureMenuItem(item, shareIntent); 250 } 251 252 /** 253 * IntentBuilder is a helper for constructing {@link Intent#ACTION_SEND} and 254 * {@link Intent#ACTION_SEND_MULTIPLE} sharing intents and starting activities 255 * to share content. The ComponentName and package name of the calling activity 256 * will be included. 257 */ 258 public static class IntentBuilder { 259 private Activity mActivity; 260 private Intent mIntent; 261 private CharSequence mChooserTitle; 262 private ArrayList<String> mToAddresses; 263 private ArrayList<String> mCcAddresses; 264 private ArrayList<String> mBccAddresses; 265 266 private ArrayList<Uri> mStreams; 267 268 /** 269 * Create a new IntentBuilder for launching a sharing action from launchingActivity. 270 * 271 * @param launchingActivity Activity that the share will be launched from 272 * @return a new IntentBuilder instance 273 */ 274 public static IntentBuilder from(Activity launchingActivity) { 275 return new IntentBuilder(launchingActivity); 276 } 277 278 private IntentBuilder(Activity launchingActivity) { 279 mActivity = launchingActivity; 280 mIntent = new Intent().setAction(Intent.ACTION_SEND); 281 mIntent.putExtra(EXTRA_CALLING_PACKAGE, launchingActivity.getPackageName()); 282 mIntent.putExtra(EXTRA_CALLING_ACTIVITY, launchingActivity.getComponentName()); 283 mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 284 } 285 286 /** 287 * Retrieve the Intent as configured so far by the IntentBuilder. This Intent 288 * is suitable for use in a ShareActionProvider or chooser dialog. 289 * 290 * <p>To create an intent that will launch the activity chooser so that the user 291 * may select a target for the share, see {@link #createChooserIntent()}. 292 * 293 * @return The current Intent being configured by this builder 294 */ 295 public Intent getIntent() { 296 if (mToAddresses != null) { 297 combineArrayExtra(Intent.EXTRA_EMAIL, mToAddresses); 298 mToAddresses = null; 299 } 300 if (mCcAddresses != null) { 301 combineArrayExtra(Intent.EXTRA_CC, mCcAddresses); 302 mCcAddresses = null; 303 } 304 if (mBccAddresses != null) { 305 combineArrayExtra(Intent.EXTRA_BCC, mBccAddresses); 306 mBccAddresses = null; 307 } 308 309 // Check if we need to change the action. 310 boolean needsSendMultiple = mStreams != null && mStreams.size() > 1; 311 boolean isSendMultiple = mIntent.getAction().equals(Intent.ACTION_SEND_MULTIPLE); 312 313 if (!needsSendMultiple && isSendMultiple) { 314 // Change back to a single send action; place the first stream into the 315 // intent for single sharing. 316 mIntent.setAction(Intent.ACTION_SEND); 317 if (mStreams != null && !mStreams.isEmpty()) { 318 mIntent.putExtra(Intent.EXTRA_STREAM, mStreams.get(0)); 319 } else { 320 mIntent.removeExtra(Intent.EXTRA_STREAM); 321 } 322 mStreams = null; 323 } 324 325 if (needsSendMultiple && !isSendMultiple) { 326 // Change to a multiple send action; place the relevant ArrayList into the 327 // intent for multiple sharing. 328 mIntent.setAction(Intent.ACTION_SEND_MULTIPLE); 329 if (mStreams != null && !mStreams.isEmpty()) { 330 mIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mStreams); 331 } else { 332 mIntent.removeExtra(Intent.EXTRA_STREAM); 333 } 334 } 335 336 return mIntent; 337 } 338 339 Activity getActivity() { 340 return mActivity; 341 } 342 343 private void combineArrayExtra(String extra, ArrayList<String> add) { 344 String[] currentAddresses = mIntent.getStringArrayExtra(extra); 345 int currentLength = currentAddresses != null ? currentAddresses.length : 0; 346 String[] finalAddresses = new String[currentLength + add.size()]; 347 add.toArray(finalAddresses); 348 if (currentAddresses != null) { 349 System.arraycopy(currentAddresses, 0, finalAddresses, add.size(), currentLength); 350 } 351 mIntent.putExtra(extra, finalAddresses); 352 } 353 354 private void combineArrayExtra(String extra, String[] add) { 355 // Add any items still pending 356 Intent intent = getIntent(); 357 String[] old = intent.getStringArrayExtra(extra); 358 int oldLength = old != null ? old.length : 0; 359 String[] result = new String[oldLength + add.length]; 360 if (old != null) System.arraycopy(old, 0, result, 0, oldLength); 361 System.arraycopy(add, 0, result, oldLength, add.length); 362 intent.putExtra(extra, result); 363 } 364 365 /** 366 * Create an Intent that will launch the standard Android activity chooser, 367 * allowing the user to pick what activity/app on the system should handle 368 * the share. 369 * 370 * @return A chooser Intent for the currently configured sharing action 371 */ 372 public Intent createChooserIntent() { 373 return Intent.createChooser(getIntent(), mChooserTitle); 374 } 375 376 /** 377 * Start a chooser activity for the current share intent. 378 * 379 * <p>Note that under most circumstances you should use 380 * {@link ShareCompat#configureMenuItem(MenuItem, IntentBuilder) 381 * ShareCompat.configureMenuItem()} to add a Share item to the menu while 382 * presenting a detail view of the content to be shared instead 383 * of invoking this directly.</p> 384 */ 385 public void startChooser() { 386 mActivity.startActivity(createChooserIntent()); 387 } 388 389 /** 390 * Set the title that will be used for the activity chooser for this share. 391 * 392 * @param title Title string 393 * @return This IntentBuilder for method chaining 394 */ 395 public IntentBuilder setChooserTitle(CharSequence title) { 396 mChooserTitle = title; 397 return this; 398 } 399 400 /** 401 * Set the title that will be used for the activity chooser for this share. 402 * 403 * @param resId Resource ID of the title string to use 404 * @return This IntentBuilder for method chaining 405 */ 406 public IntentBuilder setChooserTitle(int resId) { 407 return setChooserTitle(mActivity.getText(resId)); 408 } 409 410 /** 411 * Set the type of data being shared 412 * 413 * @param mimeType mimetype of the shared data 414 * @return This IntentBuilder for method chaining 415 * @see Intent#setType(String) 416 */ 417 public IntentBuilder setType(String mimeType) { 418 mIntent.setType(mimeType); 419 return this; 420 } 421 422 /** 423 * Set the literal text data to be sent as part of the share. 424 * This may be a styled CharSequence. 425 * 426 * @param text Text to share 427 * @return This IntentBuilder for method chaining 428 * @see Intent#EXTRA_TEXT 429 */ 430 public IntentBuilder setText(CharSequence text) { 431 mIntent.putExtra(Intent.EXTRA_TEXT, text); 432 return this; 433 } 434 435 /** 436 * Set an HTML string to be sent as part of the share. 437 * If {@link Intent#EXTRA_TEXT EXTRA_TEXT} has not already been supplied, 438 * a styled version of the supplied HTML text will be added as EXTRA_TEXT as 439 * parsed by {@link android.text.Html#fromHtml(String) Html.fromHtml}. 440 * 441 * @param htmlText A string containing HTML markup as a richer version of the text 442 * provided by EXTRA_TEXT. 443 * @return This IntentBuilder for method chaining 444 * @see #setText(CharSequence) 445 */ 446 public IntentBuilder setHtmlText(String htmlText) { 447 mIntent.putExtra(IntentCompat.EXTRA_HTML_TEXT, htmlText); 448 if (!mIntent.hasExtra(Intent.EXTRA_TEXT)) { 449 // Supply a default if EXTRA_TEXT isn't set 450 setText(Html.fromHtml(htmlText)); 451 } 452 return this; 453 } 454 455 /** 456 * Set a stream URI to the data that should be shared. 457 * 458 * <p>This replaces all currently set stream URIs and will produce a single-stream 459 * ACTION_SEND intent.</p> 460 * 461 * @param streamUri URI of the stream to share 462 * @return This IntentBuilder for method chaining 463 * @see Intent#EXTRA_STREAM 464 */ 465 public IntentBuilder setStream(Uri streamUri) { 466 if (!mIntent.getAction().equals(Intent.ACTION_SEND)) { 467 mIntent.setAction(Intent.ACTION_SEND); 468 } 469 mStreams = null; 470 mIntent.putExtra(Intent.EXTRA_STREAM, streamUri); 471 return this; 472 } 473 474 /** 475 * Add a stream URI to the data that should be shared. If this is not the first 476 * stream URI added the final intent constructed will become an ACTION_SEND_MULTIPLE 477 * intent. Not all apps will handle both ACTION_SEND and ACTION_SEND_MULTIPLE. 478 * 479 * @param streamUri URI of the stream to share 480 * @return This IntentBuilder for method chaining 481 * @see Intent#EXTRA_STREAM 482 * @see Intent#ACTION_SEND 483 * @see Intent#ACTION_SEND_MULTIPLE 484 */ 485 public IntentBuilder addStream(Uri streamUri) { 486 Uri currentStream = mIntent.getParcelableExtra(Intent.EXTRA_STREAM); 487 if (currentStream == null) { 488 return setStream(streamUri); 489 } 490 if (mStreams == null) { 491 mStreams = new ArrayList<Uri>(); 492 } 493 if (currentStream != null) { 494 mIntent.removeExtra(Intent.EXTRA_STREAM); 495 mStreams.add(currentStream); 496 } 497 mStreams.add(streamUri); 498 return this; 499 } 500 501 /** 502 * Set an array of email addresses as recipients of this share. 503 * This replaces all current "to" recipients that have been set so far. 504 * 505 * @param addresses Email addresses to send to 506 * @return This IntentBuilder for method chaining 507 * @see Intent#EXTRA_EMAIL 508 */ 509 public IntentBuilder setEmailTo(String[] addresses) { 510 if (mToAddresses != null) { 511 mToAddresses = null; 512 } 513 mIntent.putExtra(Intent.EXTRA_EMAIL, addresses); 514 return this; 515 } 516 517 /** 518 * Add an email address to be used in the "to" field of the final Intent. 519 * 520 * @param address Email address to send to 521 * @return This IntentBuilder for method chaining 522 * @see Intent#EXTRA_EMAIL 523 */ 524 public IntentBuilder addEmailTo(String address) { 525 if (mToAddresses == null) { 526 mToAddresses = new ArrayList<String>(); 527 } 528 mToAddresses.add(address); 529 return this; 530 } 531 532 /** 533 * Add an array of email addresses to be used in the "to" field of the final Intent. 534 * 535 * @param addresses Email addresses to send to 536 * @return This IntentBuilder for method chaining 537 * @see Intent#EXTRA_EMAIL 538 */ 539 public IntentBuilder addEmailTo(String[] addresses) { 540 combineArrayExtra(Intent.EXTRA_EMAIL, addresses); 541 return this; 542 } 543 544 /** 545 * Set an array of email addresses to CC on this share. 546 * This replaces all current "CC" recipients that have been set so far. 547 * 548 * @param addresses Email addresses to CC on the share 549 * @return This IntentBuilder for method chaining 550 * @see Intent#EXTRA_CC 551 */ 552 public IntentBuilder setEmailCc(String[] addresses) { 553 mIntent.putExtra(Intent.EXTRA_CC, addresses); 554 return this; 555 } 556 557 /** 558 * Add an email address to be used in the "cc" field of the final Intent. 559 * 560 * @param address Email address to CC 561 * @return This IntentBuilder for method chaining 562 * @see Intent#EXTRA_CC 563 */ 564 public IntentBuilder addEmailCc(String address) { 565 if (mCcAddresses == null) { 566 mCcAddresses = new ArrayList<String>(); 567 } 568 mCcAddresses.add(address); 569 return this; 570 } 571 572 /** 573 * Add an array of email addresses to be used in the "cc" field of the final Intent. 574 * 575 * @param addresses Email addresses to CC 576 * @return This IntentBuilder for method chaining 577 * @see Intent#EXTRA_CC 578 */ 579 public IntentBuilder addEmailCc(String[] addresses) { 580 combineArrayExtra(Intent.EXTRA_CC, addresses); 581 return this; 582 } 583 584 /** 585 * Set an array of email addresses to BCC on this share. 586 * This replaces all current "BCC" recipients that have been set so far. 587 * 588 * @param addresses Email addresses to BCC on the share 589 * @return This IntentBuilder for method chaining 590 * @see Intent#EXTRA_BCC 591 */ 592 public IntentBuilder setEmailBcc(String[] addresses) { 593 mIntent.putExtra(Intent.EXTRA_BCC, addresses); 594 return this; 595 } 596 597 /** 598 * Add an email address to be used in the "bcc" field of the final Intent. 599 * 600 * @param address Email address to BCC 601 * @return This IntentBuilder for method chaining 602 * @see Intent#EXTRA_BCC 603 */ 604 public IntentBuilder addEmailBcc(String address) { 605 if (mBccAddresses == null) { 606 mBccAddresses = new ArrayList<String>(); 607 } 608 mBccAddresses.add(address); 609 return this; 610 } 611 612 /** 613 * Add an array of email addresses to be used in the "bcc" field of the final Intent. 614 * 615 * @param addresses Email addresses to BCC 616 * @return This IntentBuilder for method chaining 617 * @see Intent#EXTRA_BCC 618 */ 619 public IntentBuilder addEmailBcc(String[] addresses) { 620 combineArrayExtra(Intent.EXTRA_BCC, addresses); 621 return this; 622 } 623 624 /** 625 * Set a subject heading for this share; useful for sharing via email. 626 * 627 * @param subject Subject heading for this share 628 * @return This IntentBuilder for method chaining 629 * @see Intent#EXTRA_SUBJECT 630 */ 631 public IntentBuilder setSubject(String subject) { 632 mIntent.putExtra(Intent.EXTRA_SUBJECT, subject); 633 return this; 634 } 635 } 636 637 /** 638 * IntentReader is a helper for reading the data contained within a sharing (ACTION_SEND) 639 * Intent. It provides methods to parse standard elements included with a share 640 * in addition to extra metadata about the app that shared the content. 641 * 642 * <p>Social sharing apps are encouraged to provide attribution for the app that shared 643 * the content. IntentReader offers access to the application label, calling activity info, 644 * and application icon of the app that shared the content. This data may have been provided 645 * voluntarily by the calling app and should always be displayed to the user before submission 646 * for manual verification. The user should be offered the option to omit this information 647 * from shared posts if desired.</p> 648 * 649 * <p>Activities that intend to receive sharing intents should configure an intent-filter 650 * to accept {@link Intent#ACTION_SEND} intents ("android.intent.action.SEND") and optionally 651 * accept {@link Intent#ACTION_SEND_MULTIPLE} ("android.intent.action.SEND_MULTIPLE") if 652 * the activity is equipped to handle multiple data streams.</p> 653 */ 654 public static class IntentReader { 655 private static final String TAG = "IntentReader"; 656 657 private Activity mActivity; 658 private Intent mIntent; 659 private String mCallingPackage; 660 private ComponentName mCallingActivity; 661 662 private ArrayList<Uri> mStreams; 663 664 /** 665 * Get an IntentReader for parsing and interpreting the sharing intent 666 * used to start the given activity. 667 * 668 * @param activity Activity that was started to share content 669 * @return IntentReader for parsing sharing data 670 */ 671 public static IntentReader from(Activity activity) { 672 return new IntentReader(activity); 673 } 674 675 private IntentReader(Activity activity) { 676 mActivity = activity; 677 mIntent = activity.getIntent(); 678 mCallingPackage = ShareCompat.getCallingPackage(activity); 679 mCallingActivity = ShareCompat.getCallingActivity(activity); 680 } 681 682 /** 683 * Returns true if the activity this reader was obtained for was 684 * started with an {@link Intent#ACTION_SEND} or {@link Intent#ACTION_SEND_MULTIPLE} 685 * sharing Intent. 686 * 687 * @return true if the activity was started with an ACTION_SEND 688 * or ACTION_SEND_MULTIPLE Intent 689 */ 690 public boolean isShareIntent() { 691 final String action = mIntent.getAction(); 692 return Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action); 693 } 694 695 /** 696 * Returns true if the activity this reader was obtained for was started with an 697 * {@link Intent#ACTION_SEND} intent and contains a single shared item. 698 * The shared content should be obtained using either the {@link #getText()} 699 * or {@link #getStream()} methods depending on the type of content shared. 700 * 701 * @return true if the activity was started with an ACTION_SEND intent 702 */ 703 public boolean isSingleShare() { 704 return Intent.ACTION_SEND.equals(mIntent.getAction()); 705 } 706 707 /** 708 * Returns true if the activity this reader was obtained for was started with an 709 * {@link Intent#ACTION_SEND_MULTIPLE} intent. The Intent may contain more than 710 * one stream item. 711 * 712 * @return true if the activity was started with an ACTION_SEND_MULTIPLE intent 713 */ 714 public boolean isMultipleShare() { 715 return Intent.ACTION_SEND_MULTIPLE.equals(mIntent.getAction()); 716 } 717 718 /** 719 * Get the mimetype of the data shared to this activity. 720 * 721 * @return mimetype of the shared data 722 * @see Intent#getType() 723 */ 724 public String getType() { 725 return mIntent.getType(); 726 } 727 728 /** 729 * Get the literal text shared with the target activity. 730 * 731 * @return Literal shared text or null if none was supplied 732 * @see Intent#EXTRA_TEXT 733 */ 734 public CharSequence getText() { 735 return mIntent.getCharSequenceExtra(Intent.EXTRA_TEXT); 736 } 737 738 /** 739 * Get the styled HTML text shared with the target activity. 740 * If no HTML text was supplied but {@link Intent#EXTRA_TEXT} contained 741 * styled text, it will be converted to HTML if possible and returned. 742 * If the text provided by {@link Intent#EXTRA_TEXT} was not styled text, 743 * it will be escaped by {@link android.text.Html#escapeHtml(CharSequence)} 744 * and returned. If no text was provided at all, this method will return null. 745 * 746 * @return Styled text provided by the sender as HTML. 747 */ 748 public String getHtmlText() { 749 String result = mIntent.getStringExtra(IntentCompat.EXTRA_HTML_TEXT); 750 if (result == null) { 751 CharSequence text = getText(); 752 if (text instanceof Spanned) { 753 result = Html.toHtml((Spanned) text); 754 } else if (text != null) { 755 result = IMPL.escapeHtml(text); 756 } 757 } 758 return result; 759 } 760 761 /** 762 * Get a URI referring to a data stream shared with the target activity. 763 * 764 * <p>This call will fail if the share intent contains multiple stream items. 765 * If {@link #isMultipleShare()} returns true the application should use 766 * {@link #getStream(int)} and {@link #getStreamCount()} to retrieve the 767 * included stream items.</p> 768 * 769 * @return A URI referring to a data stream to be shared or null if one was not supplied 770 * @see Intent#EXTRA_STREAM 771 */ 772 public Uri getStream() { 773 return mIntent.getParcelableExtra(Intent.EXTRA_STREAM); 774 } 775 776 /** 777 * Get the URI of a stream item shared with the target activity. 778 * Index should be in the range [0-getStreamCount()). 779 * 780 * @param index Index of text item to retrieve 781 * @return Requested stream item URI 782 * @see Intent#EXTRA_STREAM 783 * @see Intent#ACTION_SEND_MULTIPLE 784 */ 785 public Uri getStream(int index) { 786 if (mStreams == null && isMultipleShare()) { 787 mStreams = mIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 788 } 789 if (mStreams != null) { 790 return mStreams.get(index); 791 } 792 if (index == 0) { 793 return mIntent.getParcelableExtra(Intent.EXTRA_STREAM); 794 } 795 throw new IndexOutOfBoundsException("Stream items available: " + getStreamCount() + 796 " index requested: " + index); 797 } 798 799 /** 800 * Return the number of stream items shared. The return value will be 0 or 1 if 801 * this was an {@link Intent#ACTION_SEND} intent, or 0 or more if it was an 802 * {@link Intent#ACTION_SEND_MULTIPLE} intent. 803 * 804 * @return Count of text items contained within the Intent 805 */ 806 public int getStreamCount() { 807 if (mStreams == null && isMultipleShare()) { 808 mStreams = mIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); 809 } 810 if (mStreams != null) { 811 return mStreams.size(); 812 } 813 return mIntent.hasExtra(Intent.EXTRA_STREAM) ? 1 : 0; 814 } 815 816 /** 817 * Get an array of Strings, each an email address to share to. 818 * 819 * @return An array of email addresses or null if none were supplied. 820 * @see Intent#EXTRA_EMAIL 821 */ 822 public String[] getEmailTo() { 823 return mIntent.getStringArrayExtra(Intent.EXTRA_EMAIL); 824 } 825 826 /** 827 * Get an array of Strings, each an email address to CC on this share. 828 * 829 * @return An array of email addresses or null if none were supplied. 830 * @see Intent#EXTRA_CC 831 */ 832 public String[] getEmailCc() { 833 return mIntent.getStringArrayExtra(Intent.EXTRA_CC); 834 } 835 836 /** 837 * Get an array of Strings, each an email address to BCC on this share. 838 * 839 * @return An array of email addresses or null if none were supplied. 840 * @see Intent#EXTRA_BCC 841 */ 842 public String[] getEmailBcc() { 843 return mIntent.getStringArrayExtra(Intent.EXTRA_BCC); 844 } 845 846 /** 847 * Get a subject heading for this share; useful when sharing via email. 848 * 849 * @return The subject heading for this share or null if one was not supplied. 850 * @see Intent#EXTRA_SUBJECT 851 */ 852 public String getSubject() { 853 return mIntent.getStringExtra(Intent.EXTRA_SUBJECT); 854 } 855 856 /** 857 * Get the name of the package that invoked this sharing intent. If the activity 858 * was not started for a result, IntentBuilder will read this from extra metadata placed 859 * in the Intent by ShareBuilder. 860 * 861 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 862 * application. As such it should not be trusted for accuracy in the context of 863 * security or verification.</p> 864 * 865 * @return Name of the package that started this activity or null if unknown 866 * @see Activity#getCallingPackage() 867 * @see ShareCompat#EXTRA_CALLING_PACKAGE 868 */ 869 public String getCallingPackage() { 870 return mCallingPackage; 871 } 872 873 /** 874 * Get the {@link ComponentName} of the Activity that invoked this sharing intent. 875 * If the target sharing activity was not started for a result, IntentBuilder will read 876 * this from extra metadata placed in the intent by ShareBuilder. 877 * 878 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 879 * application. As such it should not be trusted for accuracy in the context of 880 * security or verification.</p> 881 * 882 * @return ComponentName of the calling Activity or null if unknown 883 * @see Activity#getCallingActivity() 884 * @see ShareCompat#EXTRA_CALLING_ACTIVITY 885 */ 886 public ComponentName getCallingActivity() { 887 return mCallingActivity; 888 } 889 890 /** 891 * Get the icon of the calling activity as a Drawable if data about 892 * the calling activity is available. 893 * 894 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 895 * application. As such it should not be trusted for accuracy in the context of 896 * security or verification.</p> 897 * 898 * @return The calling Activity's icon or null if unknown 899 */ 900 public Drawable getCallingActivityIcon() { 901 if (mCallingActivity == null) return null; 902 903 PackageManager pm = mActivity.getPackageManager(); 904 try { 905 return pm.getActivityIcon(mCallingActivity); 906 } catch (NameNotFoundException e) { 907 Log.e(TAG, "Could not retrieve icon for calling activity", e); 908 } 909 return null; 910 } 911 912 /** 913 * Get the icon of the calling application as a Drawable if data 914 * about the calling package is available. 915 * 916 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 917 * application. As such it should not be trusted for accuracy in the context of 918 * security or verification.</p> 919 * 920 * @return The calling application's icon or null if unknown 921 */ 922 public Drawable getCallingApplicationIcon() { 923 if (mCallingPackage == null) return null; 924 925 PackageManager pm = mActivity.getPackageManager(); 926 try { 927 return pm.getApplicationIcon(mCallingPackage); 928 } catch (NameNotFoundException e) { 929 Log.e(TAG, "Could not retrieve icon for calling application", e); 930 } 931 return null; 932 } 933 934 /** 935 * Get the human-readable label (title) of the calling application if 936 * data about the calling package is available. 937 * 938 * <p><em>Note:</em> This data may have been provided voluntarily by the calling 939 * application. As such it should not be trusted for accuracy in the context of 940 * security or verification.</p> 941 * 942 * @return The calling application's label or null if unknown 943 */ 944 public CharSequence getCallingApplicationLabel() { 945 if (mCallingPackage == null) return null; 946 947 PackageManager pm = mActivity.getPackageManager(); 948 try { 949 return pm.getApplicationLabel(pm.getApplicationInfo(mCallingPackage, 0)); 950 } catch (NameNotFoundException e) { 951 Log.e(TAG, "Could not retrieve label for calling application", e); 952 } 953 return null; 954 } 955 } 956} 957