Home | History | Annotate | Download | only in widget
      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.widget;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.content.pm.ResolveInfo;
     23 import android.graphics.drawable.Drawable;
     24 import android.util.TypedValue;
     25 import android.view.ActionProvider;
     26 import android.view.Menu;
     27 import android.view.MenuItem;
     28 import android.view.MenuItem.OnMenuItemClickListener;
     29 import android.view.SubMenu;
     30 import android.view.View;
     31 import android.widget.ActivityChooserModel.OnChooseActivityListener;
     32 
     33 import com.android.internal.R;
     34 
     35 /**
     36  * This is a provider for a share action. It is responsible for creating views
     37  * that enable data sharing and also to show a sub menu with sharing activities
     38  * if the hosting item is placed on the overflow menu.
     39  * <p>
     40  * Here is how to use the action provider with custom backing file in a {@link MenuItem}:
     41  * </p>
     42  * <pre>
     43  * // In Activity#onCreateOptionsMenu
     44  * public boolean onCreateOptionsMenu(Menu menu) {
     45  *     // Get the menu item.
     46  *     MenuItem menuItem = menu.findItem(R.id.my_menu_item);
     47  *     // Get the provider and hold onto it to set/change the share intent.
     48  *     mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
     49  *     // Set history different from the default before getting the action
     50  *     // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls
     51  *     // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this
     52  *     // line if using the default share history file is desired.
     53  *     mShareActionProvider.setShareHistoryFileName("custom_share_history.xml");
     54  *     . . .
     55  * }
     56  *
     57  * // Somewhere in the application.
     58  * public void doShare(Intent shareIntent) {
     59  *     // When you want to share set the share intent.
     60  *     mShareActionProvider.setShareIntent(shareIntent);
     61  * }</pre>
     62  * <p>
     63  * <strong>Note:</strong> While the sample snippet demonstrates how to use this provider
     64  * in the context of a menu item, the use of the provider is not limited to menu items.
     65  * </p>
     66  *
     67  * @see ActionProvider
     68  */
     69 public class ShareActionProvider extends ActionProvider {
     70 
     71     /**
     72      * Listener for the event of selecting a share target.
     73      */
     74     public interface OnShareTargetSelectedListener {
     75 
     76         /**
     77          * Called when a share target has been selected. The client can
     78          * decide whether to perform some action before the sharing is
     79          * actually performed.
     80          * <p>
     81          * <strong>Note:</strong> Modifying the intent is not permitted and
     82          *     any changes to the latter will be ignored.
     83          * </p>
     84          * <p>
     85          * <strong>Note:</strong> You should <strong>not</strong> handle the
     86          *     intent here. This callback aims to notify the client that a
     87          *     sharing is being performed, so the client can update the UI
     88          *     if necessary.
     89          * </p>
     90          *
     91          * @param source The source of the notification.
     92          * @param intent The intent for launching the chosen share target.
     93          * @return The return result is ignored. Always return false for consistency.
     94          */
     95         public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
     96     }
     97 
     98     /**
     99      * The default for the maximal number of activities shown in the sub-menu.
    100      */
    101     private static final int DEFAULT_INITIAL_ACTIVITY_COUNT = 4;
    102 
    103     /**
    104      * The the maximum number activities shown in the sub-menu.
    105      */
    106     private int mMaxShownActivityCount = DEFAULT_INITIAL_ACTIVITY_COUNT;
    107 
    108     /**
    109      * Listener for handling menu item clicks.
    110      */
    111     private final ShareMenuItemOnMenuItemClickListener mOnMenuItemClickListener =
    112         new ShareMenuItemOnMenuItemClickListener();
    113 
    114     /**
    115      * The default name for storing share history.
    116      */
    117     public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
    118 
    119     /**
    120      * Context for accessing resources.
    121      */
    122     private final Context mContext;
    123 
    124     /**
    125      * The name of the file with share history data.
    126      */
    127     private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME;
    128 
    129     private OnShareTargetSelectedListener mOnShareTargetSelectedListener;
    130 
    131     private OnChooseActivityListener mOnChooseActivityListener;
    132 
    133     /**
    134      * Creates a new instance.
    135      *
    136      * @param context Context for accessing resources.
    137      */
    138     public ShareActionProvider(Context context) {
    139         super(context);
    140         mContext = context;
    141     }
    142 
    143     /**
    144      * Sets a listener to be notified when a share target has been selected.
    145      * The listener can optionally decide to handle the selection and
    146      * not rely on the default behavior which is to launch the activity.
    147      * <p>
    148      * <strong>Note:</strong> If you choose the backing share history file
    149      *     you will still be notified in this callback.
    150      * </p>
    151      * @param listener The listener.
    152      */
    153     public void setOnShareTargetSelectedListener(OnShareTargetSelectedListener listener) {
    154         mOnShareTargetSelectedListener = listener;
    155         setActivityChooserPolicyIfNeeded();
    156     }
    157 
    158     /**
    159      * {@inheritDoc}
    160      */
    161     @Override
    162     public View onCreateActionView() {
    163         // Create the view and set its data model.
    164         ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
    165         if (!activityChooserView.isInEditMode()) {
    166             ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    167             activityChooserView.setActivityChooserModel(dataModel);
    168         }
    169 
    170         // Lookup and set the expand action icon.
    171         TypedValue outTypedValue = new TypedValue();
    172         mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
    173         Drawable drawable = mContext.getDrawable(outTypedValue.resourceId);
    174         activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
    175         activityChooserView.setProvider(this);
    176 
    177         // Set content description.
    178         activityChooserView.setDefaultActionButtonContentDescription(
    179                 R.string.shareactionprovider_share_with_application);
    180         activityChooserView.setExpandActivityOverflowButtonContentDescription(
    181                 R.string.shareactionprovider_share_with);
    182 
    183         return activityChooserView;
    184     }
    185 
    186     /**
    187      * {@inheritDoc}
    188      */
    189     @Override
    190     public boolean hasSubMenu() {
    191         return true;
    192     }
    193 
    194     /**
    195      * {@inheritDoc}
    196      */
    197     @Override
    198     public void onPrepareSubMenu(SubMenu subMenu) {
    199         // Clear since the order of items may change.
    200         subMenu.clear();
    201 
    202         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    203         PackageManager packageManager = mContext.getPackageManager();
    204 
    205         final int expandedActivityCount = dataModel.getActivityCount();
    206         final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount);
    207 
    208         // Populate the sub-menu with a sub set of the activities.
    209         for (int i = 0; i < collapsedActivityCount; i++) {
    210             ResolveInfo activity = dataModel.getActivity(i);
    211             subMenu.add(0, i, i, activity.loadLabel(packageManager))
    212                 .setIcon(activity.loadIcon(packageManager))
    213                 .setOnMenuItemClickListener(mOnMenuItemClickListener);
    214         }
    215 
    216         if (collapsedActivityCount < expandedActivityCount) {
    217             // Add a sub-menu for showing all activities as a list item.
    218             SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
    219                     collapsedActivityCount,
    220                     mContext.getString(R.string.activity_chooser_view_see_all));
    221             for (int i = 0; i < expandedActivityCount; i++) {
    222                 ResolveInfo activity = dataModel.getActivity(i);
    223                 expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
    224                     .setIcon(activity.loadIcon(packageManager))
    225                     .setOnMenuItemClickListener(mOnMenuItemClickListener);
    226             }
    227         }
    228     }
    229 
    230     /**
    231      * Sets the file name of a file for persisting the share history which
    232      * history will be used for ordering share targets. This file will be used
    233      * for all view created by {@link #onCreateActionView()}. Defaults to
    234      * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code>
    235      * if share history should not be persisted between sessions.
    236      * <p>
    237      * <strong>Note:</strong> The history file name can be set any time, however
    238      * only the action views created by {@link #onCreateActionView()} after setting
    239      * the file name will be backed by the provided file. Therefore, if you want to
    240      * use different history files for sharing specific types of content, every time
    241      * you change the history file {@link #setShareHistoryFileName(String)} you must
    242      * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the
    243      * action view. You should <strong>not</strong> call
    244      * {@link android.app.Activity#invalidateOptionsMenu()} from
    245      * {@link android.app.Activity#onCreateOptionsMenu(Menu)}.
    246      * </p>
    247      * <pre>
    248      * private void doShare(Intent intent) {
    249      *     if (IMAGE.equals(intent.getMimeType())) {
    250      *         mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME);
    251      *     } else if (TEXT.equals(intent.getMimeType())) {
    252      *         mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME);
    253      *     }
    254      *     mShareActionProvider.setIntent(intent);
    255      *     invalidateOptionsMenu();
    256      * }</pre>
    257      * @param shareHistoryFile The share history file name.
    258      */
    259     public void setShareHistoryFileName(String shareHistoryFile) {
    260         mShareHistoryFileName = shareHistoryFile;
    261         setActivityChooserPolicyIfNeeded();
    262     }
    263 
    264     /**
    265      * Sets an intent with information about the share action. Here is a
    266      * sample for constructing a share intent:
    267      * <pre>
    268      * Intent shareIntent = new Intent(Intent.ACTION_SEND);
    269      * shareIntent.setType("image/*");
    270      * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
    271      * shareIntent.putExtra(Intent.EXTRA_STREAM, uri));</pre>
    272      *
    273      * @param shareIntent The share intent.
    274      *
    275      * @see Intent#ACTION_SEND
    276      * @see Intent#ACTION_SEND_MULTIPLE
    277      */
    278     public void setShareIntent(Intent shareIntent) {
    279         if (shareIntent != null) {
    280             final String action = shareIntent.getAction();
    281             if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
    282                 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
    283                         Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    284             }
    285         }
    286         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
    287             mShareHistoryFileName);
    288         dataModel.setIntent(shareIntent);
    289     }
    290 
    291     /**
    292      * Reusable listener for handling share item clicks.
    293      */
    294     private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
    295         @Override
    296         public boolean onMenuItemClick(MenuItem item) {
    297             ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
    298                     mShareHistoryFileName);
    299             final int itemId = item.getItemId();
    300             Intent launchIntent = dataModel.chooseActivity(itemId);
    301             if (launchIntent != null) {
    302                 final String action = launchIntent.getAction();
    303                 if (Intent.ACTION_SEND.equals(action) ||
    304                         Intent.ACTION_SEND_MULTIPLE.equals(action)) {
    305                     launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
    306                             Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    307                 }
    308                 mContext.startActivity(launchIntent);
    309             }
    310             return true;
    311         }
    312     }
    313 
    314     /**
    315      * Set the activity chooser policy of the model backed by the current
    316      * share history file if needed which is if there is a registered callback.
    317      */
    318     private void setActivityChooserPolicyIfNeeded() {
    319         if (mOnShareTargetSelectedListener == null) {
    320             return;
    321         }
    322         if (mOnChooseActivityListener == null) {
    323             mOnChooseActivityListener = new ShareActivityChooserModelPolicy();
    324         }
    325         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    326         dataModel.setOnChooseActivityListener(mOnChooseActivityListener);
    327     }
    328 
    329     /**
    330      * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such.
    331      */
    332     private class ShareActivityChooserModelPolicy implements OnChooseActivityListener {
    333         @Override
    334         public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
    335             if (mOnShareTargetSelectedListener != null) {
    336                 mOnShareTargetSelectedListener.onShareTargetSelected(
    337                         ShareActionProvider.this, intent);
    338             }
    339             return false;
    340         }
    341     }
    342 }
    343