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         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    165         ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
    166         activityChooserView.setActivityChooserModel(dataModel);
    167 
    168         // Lookup and set the expand action icon.
    169         TypedValue outTypedValue = new TypedValue();
    170         mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
    171         Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
    172         activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
    173         activityChooserView.setProvider(this);
    174 
    175         // Set content description.
    176         activityChooserView.setDefaultActionButtonContentDescription(
    177                 R.string.shareactionprovider_share_with_application);
    178         activityChooserView.setExpandActivityOverflowButtonContentDescription(
    179                 R.string.shareactionprovider_share_with);
    180 
    181         return activityChooserView;
    182     }
    183 
    184     /**
    185      * {@inheritDoc}
    186      */
    187     @Override
    188     public boolean hasSubMenu() {
    189         return true;
    190     }
    191 
    192     /**
    193      * {@inheritDoc}
    194      */
    195     @Override
    196     public void onPrepareSubMenu(SubMenu subMenu) {
    197         // Clear since the order of items may change.
    198         subMenu.clear();
    199 
    200         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    201         PackageManager packageManager = mContext.getPackageManager();
    202 
    203         final int expandedActivityCount = dataModel.getActivityCount();
    204         final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount);
    205 
    206         // Populate the sub-menu with a sub set of the activities.
    207         for (int i = 0; i < collapsedActivityCount; i++) {
    208             ResolveInfo activity = dataModel.getActivity(i);
    209             subMenu.add(0, i, i, activity.loadLabel(packageManager))
    210                 .setIcon(activity.loadIcon(packageManager))
    211                 .setOnMenuItemClickListener(mOnMenuItemClickListener);
    212         }
    213 
    214         if (collapsedActivityCount < expandedActivityCount) {
    215             // Add a sub-menu for showing all activities as a list item.
    216             SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
    217                     collapsedActivityCount,
    218                     mContext.getString(R.string.activity_chooser_view_see_all));
    219             for (int i = 0; i < expandedActivityCount; i++) {
    220                 ResolveInfo activity = dataModel.getActivity(i);
    221                 expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
    222                     .setIcon(activity.loadIcon(packageManager))
    223                     .setOnMenuItemClickListener(mOnMenuItemClickListener);
    224             }
    225         }
    226     }
    227 
    228     /**
    229      * Sets the file name of a file for persisting the share history which
    230      * history will be used for ordering share targets. This file will be used
    231      * for all view created by {@link #onCreateActionView()}. Defaults to
    232      * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code>
    233      * if share history should not be persisted between sessions.
    234      * <p>
    235      * <strong>Note:</strong> The history file name can be set any time, however
    236      * only the action views created by {@link #onCreateActionView()} after setting
    237      * the file name will be backed by the provided file. Therefore, if you want to
    238      * use different history files for sharing specific types of content, every time
    239      * you change the history file {@link #setShareHistoryFileName(String)} you must
    240      * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the
    241      * action view. You should <strong>not</strong> call
    242      * {@link android.app.Activity#invalidateOptionsMenu()} from
    243      * {@link android.app.Activity#onCreateOptionsMenu(Menu)}.
    244      * </p>
    245      * <pre>
    246      * private void doShare(Intent intent) {
    247      *     if (IMAGE.equals(intent.getMimeType())) {
    248      *         mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME);
    249      *     } else if (TEXT.equals(intent.getMimeType())) {
    250      *         mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME);
    251      *     }
    252      *     mShareActionProvider.setIntent(intent);
    253      *     invalidateOptionsMenu();
    254      * }</pre>
    255      * @param shareHistoryFile The share history file name.
    256      */
    257     public void setShareHistoryFileName(String shareHistoryFile) {
    258         mShareHistoryFileName = shareHistoryFile;
    259         setActivityChooserPolicyIfNeeded();
    260     }
    261 
    262     /**
    263      * Sets an intent with information about the share action. Here is a
    264      * sample for constructing a share intent:
    265      * <pre>
    266      * Intent shareIntent = new Intent(Intent.ACTION_SEND);
    267      * shareIntent.setType("image/*");
    268      * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
    269      * shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());</pre>
    270      *
    271      * @param shareIntent The share intent.
    272      *
    273      * @see Intent#ACTION_SEND
    274      * @see Intent#ACTION_SEND_MULTIPLE
    275      */
    276     public void setShareIntent(Intent shareIntent) {
    277         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
    278             mShareHistoryFileName);
    279         dataModel.setIntent(shareIntent);
    280     }
    281 
    282     /**
    283      * Reusable listener for handling share item clicks.
    284      */
    285     private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
    286         @Override
    287         public boolean onMenuItemClick(MenuItem item) {
    288             ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
    289                     mShareHistoryFileName);
    290             final int itemId = item.getItemId();
    291             Intent launchIntent = dataModel.chooseActivity(itemId);
    292             if (launchIntent != null) {
    293                 launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    294                 mContext.startActivity(launchIntent);
    295             }
    296             return true;
    297         }
    298     }
    299 
    300     /**
    301      * Set the activity chooser policy of the model backed by the current
    302      * share history file if needed which is if there is a registered callback.
    303      */
    304     private void setActivityChooserPolicyIfNeeded() {
    305         if (mOnShareTargetSelectedListener == null) {
    306             return;
    307         }
    308         if (mOnChooseActivityListener == null) {
    309             mOnChooseActivityListener = new ShareAcitivityChooserModelPolicy();
    310         }
    311         ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
    312         dataModel.setOnChooseActivityListener(mOnChooseActivityListener);
    313     }
    314 
    315     /**
    316      * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such.
    317      */
    318     private class ShareAcitivityChooserModelPolicy implements OnChooseActivityListener {
    319         @Override
    320         public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
    321             if (mOnShareTargetSelectedListener != null) {
    322                 mOnShareTargetSelectedListener.onShareTargetSelected(
    323                         ShareActionProvider.this, intent);
    324             }
    325             return false;
    326         }
    327     }
    328 }
    329