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 
17 package android.support.v4.app;
18 
19 import static android.os.Build.VERSION.SDK_INT;
20 
21 import android.app.Activity;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.support.annotation.StringRes;
29 import android.support.v4.content.IntentCompat;
30 import android.text.Html;
31 import android.text.Spanned;
32 import android.util.Log;
33 import android.view.ActionProvider;
34 import android.view.Menu;
35 import android.view.MenuItem;
36 import android.widget.ShareActionProvider;
37 
38 import 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  */
61 public 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 
ShareCompat()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      */
getCallingPackage(Activity calledActivity)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      */
getCallingActivity(Activity calledActivity)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      */
configureMenuItem(MenuItem item, IntentBuilder shareIntent)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      */
configureMenuItem(Menu menu, int menuItemId, IntentBuilder shareIntent)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          */
from(Activity launchingActivity)209         public static IntentBuilder from(Activity launchingActivity) {
210             return new IntentBuilder(launchingActivity);
211         }
212 
IntentBuilder(Activity launchingActivity)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          */
getIntent()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 
getActivity()274         Activity getActivity() {
275             return mActivity;
276         }
277 
combineArrayExtra(String extra, ArrayList<String> add)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 
combineArrayExtra(String extra, String[] add)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          */
createChooserIntent()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          */
startChooser()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          */
setChooserTitle(CharSequence title)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          */
setChooserTitle(@tringRes int resId)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          */
setType(String mimeType)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          */
setText(CharSequence text)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          */
setHtmlText(String htmlText)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          */
setStream(Uri streamUri)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          */
addStream(Uri streamUri)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          */
setEmailTo(String[] addresses)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          */
addEmailTo(String address)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          */
addEmailTo(String[] addresses)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          */
setEmailCc(String[] addresses)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          */
addEmailCc(String address)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          */
addEmailCc(String[] addresses)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          */
setEmailBcc(String[] addresses)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          */
addEmailBcc(String address)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          */
addEmailBcc(String[] addresses)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          */
setSubject(String subject)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          */
from(Activity activity)606         public static IntentReader from(Activity activity) {
607             return new IntentReader(activity);
608         }
609 
IntentReader(Activity activity)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          */
isShareIntent()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          */
isSingleShare()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          */
isMultipleShare()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          */
getType()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          */
getText()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          */
getHtmlText()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 
withinStyle(StringBuilder out, CharSequence text, int start, int end)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          */
getStream()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          */
getStream(int index)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          */
getStreamCount()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          */
getEmailTo()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          */
getEmailCc()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          */
getEmailBcc()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          */
getSubject()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          */
getCallingPackage()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          */
getCallingActivity()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          */
getCallingActivityIcon()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          */
getCallingApplicationIcon()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          */
getCallingApplicationLabel()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