blob: c78f4ac116eebec6ebe9453844c253ae0544a772 [file] [log] [blame]
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -07001/*
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;
Svetoslav Ganov76559a62011-07-06 17:17:52 -070021import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070023import android.graphics.drawable.Drawable;
24import android.util.TypedValue;
25import android.view.ActionProvider;
Svetoslav Ganov76559a62011-07-06 17:17:52 -070026import android.view.Menu;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070027import android.view.MenuItem;
Svetoslav Ganov76559a62011-07-06 17:17:52 -070028import android.view.MenuItem.OnMenuItemClickListener;
Adam Powell961dd112011-07-12 14:25:23 -070029import android.view.SubMenu;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070030import android.view.View;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070031import android.widget.ActivityChooserModel.OnChooseActivityListener;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070032
Svetoslav Ganov76559a62011-07-06 17:17:52 -070033import com.android.internal.R;
34
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070035/**
36 * This is a provider for a share action. It is responsible for creating views
Svetoslav Ganov76559a62011-07-06 17:17:52 -070037 * 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.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070039 * <p>
40 * Here is how to use the action provider with custom backing file in a {@link MenuItem}:
41 * </p>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070042 * <pre>
kmccormick43d5abb2013-04-03 17:28:14 -070043 * // 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 * }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070056 *
kmccormick43d5abb2013-04-03 17:28:14 -070057 * // 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>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070062 * <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 */
69public class ShareActionProvider extends ActionProvider {
70
71 /**
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070072 * 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
Svetoslav Ganovb33eacd2012-04-13 12:32:17 -070078 * decide whether to perform some action before the sharing is
79 * actually performed.
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070080 * <p>
81 * <strong>Note:</strong> Modifying the intent is not permitted and
82 * any changes to the latter will be ignored.
83 * </p>
Svetoslav Ganovb33eacd2012-04-13 12:32:17 -070084 * <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>
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070090 *
91 * @param source The source of the notification.
92 * @param intent The intent for launching the chosen share target.
Svetoslav Ganovb33eacd2012-04-13 12:32:17 -070093 * @return The return result is ignored. Always return false for consistency.
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070094 */
95 public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
96 }
97
98 /**
Svetoslav Ganov76559a62011-07-06 17:17:52 -070099 * 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 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700115 * The default name for storing share history.
116 */
117 public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
118
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700119 /**
120 * Context for accessing resources.
121 */
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700122 private final Context mContext;
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700123
124 /**
125 * The name of the file with share history data.
126 */
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700127 private String mShareHistoryFileName = DEFAULT_SHARE_HISTORY_FILE_NAME;
128
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700129 private OnShareTargetSelectedListener mOnShareTargetSelectedListener;
130
131 private OnChooseActivityListener mOnChooseActivityListener;
132
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700133 /**
134 * Creates a new instance.
135 *
136 * @param context Context for accessing resources.
137 */
138 public ShareActionProvider(Context context) {
139 super(context);
140 mContext = context;
141 }
142
143 /**
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700144 * 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 */
153 public void setOnShareTargetSelectedListener(OnShareTargetSelectedListener listener) {
154 mOnShareTargetSelectedListener = listener;
155 setActivityChooserPolicyIfNeeded();
156 }
157
158 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700159 * {@inheritDoc}
160 */
161 @Override
162 public View onCreateActionView() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700163 // Create the view and set its data model.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700164 ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
Deepanshu Gupta14bf0ce2013-12-12 12:16:24 -0800165 if (!activityChooserView.isInEditMode()) {
166 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
167 activityChooserView.setActivityChooserModel(dataModel);
168 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700169
170 // Lookup and set the expand action icon.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700171 TypedValue outTypedValue = new TypedValue();
172 mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800173 Drawable drawable = mContext.getDrawable(outTypedValue.resourceId);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700174 activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
Adam Powell823f0742011-09-21 17:17:01 -0700175 activityChooserView.setProvider(this);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700176
Svetoslav Ganov70853772011-09-30 19:57:35 -0700177 // Set content description.
178 activityChooserView.setDefaultActionButtonContentDescription(
179 R.string.shareactionprovider_share_with_application);
180 activityChooserView.setExpandActivityOverflowButtonContentDescription(
181 R.string.shareactionprovider_share_with);
182
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700183 return activityChooserView;
184 }
185
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700186 /**
187 * {@inheritDoc}
188 */
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700189 @Override
Adam Powell961dd112011-07-12 14:25:23 -0700190 public boolean hasSubMenu() {
191 return true;
192 }
193
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700194 /**
195 * {@inheritDoc}
196 */
Adam Powell961dd112011-07-12 14:25:23 -0700197 @Override
198 public void onPrepareSubMenu(SubMenu subMenu) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700199 // 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
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700216 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 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700227 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700228 }
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
Svetoslav Ganov775bcac2012-02-07 12:18:04 -0800239 * 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
kmccormick43d5abb2013-04-03 17:28:14 -0700245 * {@link android.app.Activity#onCreateOptionsMenu(Menu)}.
246 * </p>
247 * <pre>
Svetoslav Ganov775bcac2012-02-07 12:18:04 -0800248 * 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();
kmccormick43d5abb2013-04-03 17:28:14 -0700256 * }</pre>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700257 * @param shareHistoryFile The share history file name.
258 */
259 public void setShareHistoryFileName(String shareHistoryFile) {
260 mShareHistoryFileName = shareHistoryFile;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700261 setActivityChooserPolicyIfNeeded();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700262 }
263
264 /**
265 * Sets an intent with information about the share action. Here is a
266 * sample for constructing a share intent:
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700267 * <pre>
kmccormick43d5abb2013-04-03 17:28:14 -0700268 * Intent shareIntent = new Intent(Intent.ACTION_SEND);
269 * shareIntent.setType("image/*");
270 * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
kopriva7ecfe312018-09-17 15:17:50 -0700271 * shareIntent.putExtra(Intent.EXTRA_STREAM, uri);</pre>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700272 *
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700273 * @param shareIntent The share intent.
274 *
275 * @see Intent#ACTION_SEND
276 * @see Intent#ACTION_SEND_MULTIPLE
277 */
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700278 public void setShareIntent(Intent shareIntent) {
Craig Mautner411d2aed2014-05-08 09:07:43 -0700279 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 |
Dianne Hackborn13420f22014-07-18 15:43:56 -0700283 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700284 }
285 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700286 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
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);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700301 if (launchIntent != null) {
Craig Mautner411d2aed2014-05-08 09:07:43 -0700302 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 |
Dianne Hackborn13420f22014-07-18 15:43:56 -0700306 Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
Craig Mautner411d2aed2014-05-08 09:07:43 -0700307 }
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700308 mContext.startActivity(launchIntent);
309 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700310 return true;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700311 }
312 }
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700313
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 */
318 private void setActivityChooserPolicyIfNeeded() {
319 if (mOnShareTargetSelectedListener == null) {
320 return;
321 }
322 if (mOnChooseActivityListener == null) {
Craig Mautner41db4a72014-05-07 17:20:56 -0700323 mOnChooseActivityListener = new ShareActivityChooserModelPolicy();
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700324 }
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 */
Craig Mautner41db4a72014-05-07 17:20:56 -0700332 private class ShareActivityChooserModelPolicy implements OnChooseActivityListener {
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700333 @Override
334 public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
335 if (mOnShareTargetSelectedListener != null) {
Svetoslav Ganovb33eacd2012-04-13 12:32:17 -0700336 mOnShareTargetSelectedListener.onShareTargetSelected(
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700337 ShareActionProvider.this, intent);
338 }
339 return false;
340 }
341 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700342}