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