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