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