ShareActionProvider.java revision 775bcac6bcdd04b6bffd23b5de0da4588a749337
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. Therefore, if you want to
244     * use different history files for sharing specific types of content, every time
245     * you change the history file {@link #setShareHistoryFileName(String)} you must
246     * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the
247     * action view. You should <strong>not</strong> call
248     * {@link android.app.Activity#invalidateOptionsMenu()} from
249     * {@link android.app.Activity#onCreateOptionsMenu(Menu)}.
250     * <p>
251     * <code>
252     * private void doShare(Intent intent) {
253     *     if (IMAGE.equals(intent.getMimeType())) {
254     *         mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME);
255     *     } else if (TEXT.equals(intent.getMimeType())) {
256     *         mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME);
257     *     }
258     *     mShareActionProvider.setIntent(intent);
259     *     invalidateOptionsMenu();
260     * }
261     * <code>
262     *
263     * @param shareHistoryFile The share history file name.
264     */
265    public void setShareHistoryFileName(String shareHistoryFile) {
266        mShareHistoryFileName = shareHistoryFile;
267        setActivityChooserPolicyIfNeeded();
268    }
269
270    /**
271     * Sets an intent with information about the share action. Here is a
272     * sample for constructing a share intent:
273     * <p>
274     * <pre>
275     * <code>
276     *  Intent shareIntent = new Intent(Intent.ACTION_SEND);
277     *  shareIntent.setType("image/*");
278     *  Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
279     *  shareIntent.putExtra(Intent.EXTRA_STREAM, uri.toString());
280     * </pre>
281     * </code>
282     * </p>
283     *
284     * @param shareIntent The share intent.
285     *
286     * @see Intent#ACTION_SEND
287     * @see Intent#ACTION_SEND_MULTIPLE
288     */
289    public void setShareIntent(Intent shareIntent) {
290        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
291            mShareHistoryFileName);
292        dataModel.setIntent(shareIntent);
293    }
294
295    /**
296     * Reusable listener for handling share item clicks.
297     */
298    private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
299        @Override
300        public boolean onMenuItemClick(MenuItem item) {
301            ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
302                    mShareHistoryFileName);
303            final int itemId = item.getItemId();
304            Intent launchIntent = dataModel.chooseActivity(itemId);
305            if (launchIntent != null) {
306                launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
307                mContext.startActivity(launchIntent);
308            }
309            return true;
310        }
311    }
312
313    /**
314     * Set the activity chooser policy of the model backed by the current
315     * share history file if needed which is if there is a registered callback.
316     */
317    private void setActivityChooserPolicyIfNeeded() {
318        if (mOnShareTargetSelectedListener == null) {
319            return;
320        }
321        if (mOnChooseActivityListener == null) {
322            mOnChooseActivityListener = new ShareAcitivityChooserModelPolicy();
323        }
324        ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
325        dataModel.setOnChooseActivityListener(mOnChooseActivityListener);
326    }
327
328    /**
329     * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such.
330     */
331    private class ShareAcitivityChooserModelPolicy implements OnChooseActivityListener {
332        @Override
333        public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
334            if (mOnShareTargetSelectedListener != null) {
335                mOnShareTargetSelectedListener.onShareTargetSelected(
336                        ShareActionProvider.this, intent);
337            }
338            return false;
339        }
340    }
341}
342