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