blob: bdaaa0164ceb554bf17609ea81e3b521777e1151 [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 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
165 ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
166 activityChooserView.setActivityChooserModel(dataModel);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700167
168 // Lookup and set the expand action icon.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700169 TypedValue outTypedValue = new TypedValue();
170 mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
171 Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
172 activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
Adam Powell823f0742011-09-21 17:17:01 -0700173 activityChooserView.setProvider(this);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700174
Svetoslav Ganov70853772011-09-30 19:57:35 -0700175 // Set content description.
176 activityChooserView.setDefaultActionButtonContentDescription(
177 R.string.shareactionprovider_share_with_application);
178 activityChooserView.setExpandActivityOverflowButtonContentDescription(
179 R.string.shareactionprovider_share_with);
180
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700181 return activityChooserView;
182 }
183
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700184 /**
185 * {@inheritDoc}
186 */
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700187 @Override
Adam Powell961dd112011-07-12 14:25:23 -0700188 public boolean hasSubMenu() {
189 return true;
190 }
191
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700192 /**
193 * {@inheritDoc}
194 */
Adam Powell961dd112011-07-12 14:25:23 -0700195 @Override
196 public void onPrepareSubMenu(SubMenu subMenu) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700197 // Clear since the order of items may change.
198 subMenu.clear();
199
200 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
201 PackageManager packageManager = mContext.getPackageManager();
202
203 final int expandedActivityCount = dataModel.getActivityCount();
204 final int collapsedActivityCount = Math.min(expandedActivityCount, mMaxShownActivityCount);
205
206 // Populate the sub-menu with a sub set of the activities.
207 for (int i = 0; i < collapsedActivityCount; i++) {
208 ResolveInfo activity = dataModel.getActivity(i);
209 subMenu.add(0, i, i, activity.loadLabel(packageManager))
210 .setIcon(activity.loadIcon(packageManager))
211 .setOnMenuItemClickListener(mOnMenuItemClickListener);
212 }
213
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700214 if (collapsedActivityCount < expandedActivityCount) {
215 // Add a sub-menu for showing all activities as a list item.
216 SubMenu expandedSubMenu = subMenu.addSubMenu(Menu.NONE, collapsedActivityCount,
217 collapsedActivityCount,
218 mContext.getString(R.string.activity_chooser_view_see_all));
219 for (int i = 0; i < expandedActivityCount; i++) {
220 ResolveInfo activity = dataModel.getActivity(i);
221 expandedSubMenu.add(0, i, i, activity.loadLabel(packageManager))
222 .setIcon(activity.loadIcon(packageManager))
223 .setOnMenuItemClickListener(mOnMenuItemClickListener);
224 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700225 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700226 }
227
228 /**
229 * Sets the file name of a file for persisting the share history which
230 * history will be used for ordering share targets. This file will be used
231 * for all view created by {@link #onCreateActionView()}. Defaults to
232 * {@link #DEFAULT_SHARE_HISTORY_FILE_NAME}. Set to <code>null</code>
233 * if share history should not be persisted between sessions.
234 * <p>
235 * <strong>Note:</strong> The history file name can be set any time, however
236 * only the action views created by {@link #onCreateActionView()} after setting
Svetoslav Ganov775bcac2012-02-07 12:18:04 -0800237 * the file name will be backed by the provided file. Therefore, if you want to
238 * use different history files for sharing specific types of content, every time
239 * you change the history file {@link #setShareHistoryFileName(String)} you must
240 * call {@link android.app.Activity#invalidateOptionsMenu()} to recreate the
241 * action view. You should <strong>not</strong> call
242 * {@link android.app.Activity#invalidateOptionsMenu()} from
kmccormick43d5abb2013-04-03 17:28:14 -0700243 * {@link android.app.Activity#onCreateOptionsMenu(Menu)}.
244 * </p>
245 * <pre>
Svetoslav Ganov775bcac2012-02-07 12:18:04 -0800246 * private void doShare(Intent intent) {
247 * if (IMAGE.equals(intent.getMimeType())) {
248 * mShareActionProvider.setHistoryFileName(SHARE_IMAGE_HISTORY_FILE_NAME);
249 * } else if (TEXT.equals(intent.getMimeType())) {
250 * mShareActionProvider.setHistoryFileName(SHARE_TEXT_HISTORY_FILE_NAME);
251 * }
252 * mShareActionProvider.setIntent(intent);
253 * invalidateOptionsMenu();
kmccormick43d5abb2013-04-03 17:28:14 -0700254 * }</pre>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700255 * @param shareHistoryFile The share history file name.
256 */
257 public void setShareHistoryFileName(String shareHistoryFile) {
258 mShareHistoryFileName = shareHistoryFile;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700259 setActivityChooserPolicyIfNeeded();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700260 }
261
262 /**
263 * Sets an intent with information about the share action. Here is a
264 * sample for constructing a share intent:
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700265 * <pre>
kmccormick43d5abb2013-04-03 17:28:14 -0700266 * Intent shareIntent = new Intent(Intent.ACTION_SEND);
267 * shareIntent.setType("image/*");
268 * Uri uri = Uri.fromFile(new File(getFilesDir(), "foo.jpg"));
Katie McCormickf2ef28d2013-11-26 11:10:54 -0800269 * shareIntent.putExtra(Intent.EXTRA_STREAM, uri));</pre>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700270 *
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700271 * @param shareIntent The share intent.
272 *
273 * @see Intent#ACTION_SEND
274 * @see Intent#ACTION_SEND_MULTIPLE
275 */
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700276 public void setShareIntent(Intent shareIntent) {
277 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
278 mShareHistoryFileName);
279 dataModel.setIntent(shareIntent);
280 }
281
282 /**
283 * Reusable listener for handling share item clicks.
284 */
285 private class ShareMenuItemOnMenuItemClickListener implements OnMenuItemClickListener {
286 @Override
287 public boolean onMenuItemClick(MenuItem item) {
288 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext,
289 mShareHistoryFileName);
290 final int itemId = item.getItemId();
291 Intent launchIntent = dataModel.chooseActivity(itemId);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700292 if (launchIntent != null) {
Adam Powell314419c2012-01-24 13:33:09 -0800293 launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700294 mContext.startActivity(launchIntent);
295 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700296 return true;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700297 }
298 }
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700299
300 /**
301 * Set the activity chooser policy of the model backed by the current
302 * share history file if needed which is if there is a registered callback.
303 */
304 private void setActivityChooserPolicyIfNeeded() {
305 if (mOnShareTargetSelectedListener == null) {
306 return;
307 }
308 if (mOnChooseActivityListener == null) {
309 mOnChooseActivityListener = new ShareAcitivityChooserModelPolicy();
310 }
311 ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
312 dataModel.setOnChooseActivityListener(mOnChooseActivityListener);
313 }
314
315 /**
316 * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such.
317 */
318 private class ShareAcitivityChooserModelPolicy implements OnChooseActivityListener {
319 @Override
320 public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
321 if (mOnShareTargetSelectedListener != null) {
Svetoslav Ganovb33eacd2012-04-13 12:32:17 -0700322 mOnShareTargetSelectedListener.onShareTargetSelected(
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700323 ShareActionProvider.this, intent);
324 }
325 return false;
326 }
327 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700328}