Home | History | Annotate | Download | only in app
      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 
     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