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