ShareActionProvider.java revision 340e2611de6d54516e222597585dbe7968a9915d
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
17package android.widget;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.graphics.drawable.Drawable;
24import android.util.TypedValue;
25import android.view.ActionProvider;
26import android.view.Menu;
27import android.view.MenuItem;
28import android.view.MenuItem.OnMenuItemClickListener;
29import android.view.SubMenu;
30import android.view.View;
31import android.widget.ActivityChooserModel.OnChooseActivityListener;
32
33import 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 *      getManuInflater().inflate(R.menu.my_menu, menu);
48 *      // Get the menu item.
49 *      MenuItem menuItem = menu.findItem(R.id.my_menu_item);
50 *      // Get the provider and hold onto it to set/change the share intent.
51 *      mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
52 *      // Set history different from the default before getting the action
53 *      // view since a call to {@link MenuItem#getActionView() MenuItem.getActionView()} calls
54 *      // {@link ActionProvider#onCreateActionView()} which uses the backing file name. Omit this
55 *      // line if using the default share history file is desired.
56 *      mShareActionProvider.setShareHistoryFileName("custom_share_history.xml");
57 *      . . .
58 *  }
59 *
60 *  // Somewhere in the application.
61 *  public void doShare(Intent shareIntent) {
62 *      // When you want to share set the share intent.
63 *      mShareActionProvider.setShareIntent(shareIntent);
64 *  }
65 * </pre>
66 * </code>
67 * </p>
68 * <p>
69 * <strong>Note:</strong> While the sample snippet demonstrates how to use this provider
70 * in the context of a menu item, the use of the provider is not limited to menu items.
71 * </p>
72 *
73 * @see ActionProvider
74 */
75public class ShareActionProvider extends ActionProvider {
76
77    /**
78     * Listener for the event of selecting a share target.
79     */
80    public interface OnShareTargetSelectedListener {
81
82        /**
83         * Called when a share target has been selected. The client can
84         * decide whether to perform some action before the sharing is
85         * actually performed.
86         * <p>
87         * <strong>Note:</strong> Modifying the intent is not permitted and
88         *     any changes to the latter will be ignored.
89         * </p>
90         * <p>
91         * <strong>Note:</strong> You should <strong>not</strong> handle the
92         *     intent here. This callback aims to notify the client that a
93         *     sharing is being performed, so the client can update the UI
94         *     if necessary.
95         * </p>
96         *
97         * @param source The source of the notification.
98         * @param intent The intent for launching the chosen share target.
99         * @return The return result is ignored. Always return false for consistency.
100         */
101        public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
102    }
103
104    /**
105     * The default for the maximal number of activities shown in the sub-menu.
106     */
107    private static final int DEFAULT_INITIAL_ACTIVITY_COUNT = 4;
108
109    /**
110     * The the maximum number activities shown in the sub-menu.
111     */
112    private int mMaxShownActivityCount = DEFAULT_INITIAL_ACTIVITY_COUNT;
113
114    /**
115     * Listener for handling menu item clicks.
116     */
117    private final ShareMenuItemOnMenuItemClickListener mOnMenuItemClickListener =
118        new ShareMenuItemOnMenuItemClickListener();
119
120    /**
121     * The default name for storing share history.
122     */
123    public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
124
125    /**
126     * Context for accessing resources.
127     */
128    private final Context mContext;
129
130    /**
131     * The name of the file with share history data.
132     */
133    private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME;
134
135    private OnShareTargetSelectedListener mOnShareTargetSelectedListener;
136
137    private OnChooseActivityListener mOnChooseActivityListener;
138
139    /**
140     * Creates a new instance.
141     *
142     * @param context Context for accessing resources.
143     */
144    public ShareActionProvider(Context context) {
145        super(context);
146        mContext = context;
147    }
148
149    /**
150     * Sets a listener to be notified when a share target has been selected.
151     * The listener can optionally decide to handle the selection and
152     * not rely on the default behavior which is to launch the activity.
153     * <p>
154     * <strong>Note:</strong> If you choose the backing share history file
155     *     you will still be notified in this callback.
156     * </p>
157     * @param listener The listener.
158     */
159    public void setOnShareTargetSelectedListener(OnShareTargetSelectedListener listener) {
160        mOnShareTargetSelectedListener = listener;
161        setActivityChooserPolicyIfNeeded();
162    }
163
164    /**
165     * {@inheritDoc}
166     */
167    @Override
168    public View onCreateActionView() {
169        // Create the view and set its data model.
170        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
171        ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
172        activityChooserView.setActivityChooserModel(dataModel);
173
174        // Lookup and set the expand action icon.
175        TypedValue outTypedValue = new TypedValue();
176        mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
177        Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
178        activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
179        activityChooserView.setProvider(this);
180
181        // Set content description.
182        activityChooserView.setDefaultActionButtonContentDescription(
183                R.string.shareactionprovider_share_with_application);
184        activityChooserView.setExpandActivityOverflowButtonContentDescription(
185                R.string.shareactionprovider_share_with);
186
187        return activityChooserView;
188    }
189
190    /**
191     * {@inheritDoc}
192     */
193    @Override
194    public boolean hasSubMenu() {
195        return true;
196    }
197
198    /**
199     * {@inheritDoc}
200     */
201    @Override
202    public void onPrepareSubMenu(SubMenu subMenu) {
203        // Clear since the order of items may change.
204        subMenu.clear();
205
206        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
207        PackageManager packageManager = mContext.getPackageManager();
208
209        final int expandedActivityCount = dataModel.getActivityCount();
210        final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount);
211
212        // Populate the sub-menu with a sub set of the activities.
213        for (int i = 0; i < collapsedActivityCount; i++) {
214            ResolveInfo activity = dataModel.getActivity(i);
215            subMenu.add(0, i, i, activity.loadLabel(packageManager))
216                .setIcon(activity.loadIcon(packageManager))
217                .setOnMenuItemClickListener(mOnMenuItemClickListener);
218        }
219
220        if (collapsedActivityCount < expandedActivityCount) {
221            // Add a sub-menu for showing all activities as a list item.
222            SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
223                    collapsedActivityCount,
224                    mContext.getString(R.string.activity_chooser_view_see_all));
225            for (int i = 0; i < expandedActivityCount; i++) {
226                ResolveInfo activity = dataModel.getActivity(i);
227                expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
228                    .setIcon(activity.loadIcon(packageManager))
229                    .setOnMenuItemClickListener(mOnMenuItemClickListener);
230            }
231        }
232    }
233
234    /**
235     * Sets the file name of a file for persisting the share history which
236     * history will be used for ordering share targets. This file will be used
237     * for all view created by {@link #onCreateActionView()}. Defaults to
238     * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code>
239     * if share history should not be persisted between sessions.
240     * <p>
241     * <strong>Note:</strong> The history file name can be set any time, however
242     * only the action views created by {@link #onCreateActionView()} after setting
243     * the file name will be backed by the provided file. Hence, if you are using
244     * a share action provider on a menu item and want to change the history file
245     * based on the type of the currently selected item, you need to call
246     * {@link android.app.Activity#invalidateOptionsMenu()} to force the system
247     * to recreate the menu UI.
248     * <p>
249     *
250     * @param shareHistoryFile The share history file name.
251     */
252    public void setShareHistoryFileName(String shareHistoryFile) {
253        mShareHistoryFileName = shareHistoryFile;
254        setActivityChooserPolicyIfNeeded();
255    }
256
257    /**
258     * Sets an intent with information about the share action. Here is a
259     * sample for constructing a share intent:
260     * <p>
261     * <pre>
262     * <code>
263     *  Intent shareIntent = new Intent(Intent.ACTION_SEND);
264     *  shareIntent.setType("image/*");
265     *  Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
266     *  shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());
267     * </pre>
268     * </code>
269     * </p>
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