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