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