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