blob: aa18d576c8f47a85bb3dcfd3f055eb72e0cc1e72 [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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.StringRes;
Artur Satayevad9254c2019-12-10 17:47:54 +000020import android.compat.annotation.UnsupportedAppUsage;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070021import android.content.Context;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070022import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
Svetoslav Ganov414051b2011-07-17 22:28:42 -070025import android.content.res.Resources;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070026import android.content.res.TypedArray;
27import android.database.DataSetObserver;
sergeyve471a4e2017-05-25 15:49:40 -070028import android.graphics.Color;
29import android.graphics.drawable.ColorDrawable;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070030import android.graphics.drawable.Drawable;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070031import android.util.AttributeSet;
Svetoslavbaeabb62013-10-28 15:22:14 -070032import android.util.Log;
Adam Powell823f0742011-09-21 17:17:01 -070033import android.view.ActionProvider;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070034import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -070037import android.view.ViewTreeObserver;
38import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Alan Viverette058ac7c2013-08-19 16:44:30 -070039import android.view.accessibility.AccessibilityNodeInfo;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070040import android.widget.ActivityChooserModel.ActivityChooserModelClient;
41
Aurimas Liutikas99441c52016-10-11 16:48:32 -070042import com.android.internal.R;
43import com.android.internal.view.menu.ShowableListMenu;
44
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070045/**
46 * This class is a view for choosing an activity for handling a given {@link Intent}.
47 * <p>
48 * The view is composed of two adjacent buttons:
49 * <ul>
50 * <li>
51 * The left button is an immediate action and allows one click activity choosing.
52 * Tapping this button immediately executes the intent without requiring any further
53 * user input. Long press on this button shows a popup for changing the default
54 * activity.
55 * </li>
56 * <li>
57 * The right button is an overflow action and provides an optimized menu
58 * of additional activities. Tapping this button shows a popup anchored to this
59 * view, listing the most frequently used activities. This list is initially
60 * limited to a small number of items in frequency used order. The last item,
61 * "Show all..." serves as an affordance to display all available activities.
62 * </li>
63 * </ul>
64 * </p>
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070065 *
66 * @hide
67 */
68public class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient {
69
Svetoslavbaeabb62013-10-28 15:22:14 -070070 private static final String LOG_TAG = "ActivityChooserView";
71
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070072 /**
73 * An adapter for displaying the activities in an {@link AdapterView}.
74 */
75 private final ActivityChooserViewAdapter mAdapter;
76
77 /**
78 * Implementation of various interfaces to avoid publishing them in the APIs.
79 */
80 private final Callbacks mCallbacks;
81
82 /**
83 * The content of this view.
84 */
85 private final LinearLayout mActivityChooserContent;
86
87 /**
Svetoslav Ganovf2e75402011-09-07 16:38:40 -070088 * Stores the background drawable to allow hiding and latter showing.
89 */
90 private final Drawable mActivityChooserContentBackground;
91
92 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -070093 * The expand activities action button;
94 */
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -070095 private final FrameLayout mExpandActivityOverflowButton;
96
97 /**
98 * The image for the expand activities action button;
99 */
100 private final ImageView mExpandActivityOverflowButtonImage;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700101
102 /**
103 * The default activities action button;
104 */
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700105 private final FrameLayout mDefaultActivityButton;
106
107 /**
108 * The image for the default activities action button;
109 */
110 private final ImageView mDefaultActivityButtonImage;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700111
112 /**
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700113 * The maximal width of the list popup.
114 */
115 private final int mListPopupMaxWidth;
116
117 /**
Adam Powell823f0742011-09-21 17:17:01 -0700118 * The ActionProvider hosting this view, if applicable.
119 */
120 ActionProvider mProvider;
121
122 /**
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700123 * Observer for the model data.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700124 */
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700125 private final DataSetObserver mModelDataSetOberver = new DataSetObserver() {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700126
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700127 @Override
128 public void onChanged() {
129 super.onChanged();
130 mAdapter.notifyDataSetChanged();
131 }
132 @Override
133 public void onInvalidated() {
134 super.onInvalidated();
135 mAdapter.notifyDataSetInvalidated();
136 }
137 };
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700138
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700139 private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() {
140 @Override
141 public void onGlobalLayout() {
142 if (isShowingPopup()) {
143 if (!isShown()) {
144 getListPopupWindow().dismiss();
145 } else {
146 getListPopupWindow().show();
Adam Powell823f0742011-09-21 17:17:01 -0700147 if (mProvider != null) {
148 mProvider.subUiVisibilityChanged(true);
149 }
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700150 }
151 }
152 }
153 };
154
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700155 /**
156 * Popup window for showing the activity overflow list.
157 */
158 private ListPopupWindow mListPopupWindow;
159
160 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700161 * Listener for the dismissal of the popup/alert.
162 */
163 private PopupWindow.OnDismissListener mOnDismissListener;
164
165 /**
166 * Flag whether a default activity currently being selected.
167 */
168 private boolean mIsSelectingDefaultActivity;
169
170 /**
171 * The count of activities in the popup.
172 */
173 private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT;
174
175 /**
176 * Flag whether this view is attached to a window.
177 */
178 private boolean mIsAttachedToWindow;
179
180 /**
Svetoslav Ganov70853772011-09-30 19:57:35 -0700181 * String resource for formatting content description of the default target.
182 */
183 private int mDefaultActionButtonContentDescription;
184
185 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700186 * Create a new instance.
187 *
188 * @param context The application environment.
189 */
190 public ActivityChooserView(Context context) {
191 this(context, null);
192 }
193
194 /**
195 * Create a new instance.
196 *
197 * @param context The application environment.
198 * @param attrs A collection of attributes.
199 */
200 public ActivityChooserView(Context context, AttributeSet attrs) {
Adam Powell23f4cc02011-08-18 10:30:46 -0700201 this(context, attrs, 0);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700202 }
203
204 /**
205 * Create a new instance.
206 *
207 * @param context The application environment.
208 * @param attrs A collection of attributes.
Alan Viverette617feb92013-09-09 18:09:13 -0700209 * @param defStyleAttr An attribute in the current theme that contains a
210 * reference to a style resource that supplies default values for
211 * the view. Can be 0 to not look for defaults.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700212 */
Alan Viverette617feb92013-09-09 18:09:13 -0700213 public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) {
214 this(context, attrs, defStyleAttr, 0);
215 }
216
217 /**
218 * Create a new instance.
219 *
220 * @param context The application environment.
221 * @param attrs A collection of attributes.
222 * @param defStyleAttr An attribute in the current theme that contains a
223 * reference to a style resource that supplies default values for
224 * the view. Can be 0 to not look for defaults.
225 * @param defStyleRes A resource identifier of a style resource that
226 * supplies default values for the view, used only if
227 * defStyleAttr is 0 or can not be found in the theme. Can be 0
228 * to not look for defaults.
229 */
230 public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
231 super(context, attrs, defStyleAttr, defStyleRes);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700232
233 TypedArray attributesArray = context.obtainStyledAttributes(attrs,
Alan Viverette617feb92013-09-09 18:09:13 -0700234 R.styleable.ActivityChooserView, defStyleAttr, defStyleRes);
Aurimas Liutikasab324cf2019-02-07 16:46:38 -0800235 saveAttributeDataForStyleable(context, R.styleable.ActivityChooserView, attrs,
236 attributesArray, defStyleAttr, defStyleRes);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700237
238 mInitialActivityCount = attributesArray.getInt(
239 R.styleable.ActivityChooserView_initialActivityCount,
240 ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT);
241
242 Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable(
243 R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable);
244
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700245 attributesArray.recycle();
246
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700247 LayoutInflater inflater = LayoutInflater.from(mContext);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700248 inflater.inflate(R.layout.activity_chooser_view, this, true);
249
250 mCallbacks = new Callbacks();
251
252 mActivityChooserContent = (LinearLayout) findViewById(R.id.activity_chooser_view_content);
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700253 mActivityChooserContentBackground = mActivityChooserContent.getBackground();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700254
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700255 mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button);
256 mDefaultActivityButton.setOnClickListener(mCallbacks);
257 mDefaultActivityButton.setOnLongClickListener(mCallbacks);
Alan Viverette8e1a7292017-02-27 10:57:58 -0500258 mDefaultActivityButtonImage = mDefaultActivityButton.findViewById(R.id.image);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700259
Alan Viverette5d46c892013-09-03 14:56:26 -0700260 final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
261 expandButton.setOnClickListener(mCallbacks);
262 expandButton.setAccessibilityDelegate(new AccessibilityDelegate() {
Alan Viverette058ac7c2013-08-19 16:44:30 -0700263 @Override
264 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
265 super.onInitializeAccessibilityNodeInfo(host, info);
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700266 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700267 }
268 });
Alan Viverette5d46c892013-09-03 14:56:26 -0700269 expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
270 @Override
Oren Blasbergf44d90b2015-08-31 14:15:26 -0700271 public ShowableListMenu getPopup() {
Alan Viverette5d46c892013-09-03 14:56:26 -0700272 return getListPopupWindow();
273 }
274
275 @Override
276 protected boolean onForwardingStarted() {
277 showPopup();
278 return true;
279 }
280
281 @Override
282 protected boolean onForwardingStopped() {
283 dismissPopup();
284 return true;
285 }
286 });
287 mExpandActivityOverflowButton = expandButton;
288
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700289 mExpandActivityOverflowButtonImage =
Alan Viverette8e1a7292017-02-27 10:57:58 -0500290 expandButton.findViewById(R.id.image);
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700291 mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700292
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700293 mAdapter = new ActivityChooserViewAdapter();
294 mAdapter.registerDataSetObserver(new DataSetObserver() {
295 @Override
296 public void onChanged() {
297 super.onChanged();
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700298 updateAppearance();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700299 }
300 });
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700301
302 Resources resources = context.getResources();
303 mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
304 resources.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700305 }
306
307 /**
308 * {@inheritDoc}
309 */
310 public void setActivityChooserModel(ActivityChooserModel dataModel) {
311 mAdapter.setDataModel(dataModel);
312 if (isShowingPopup()) {
313 dismissPopup();
314 showPopup();
315 }
316 }
317
318 /**
319 * Sets the background for the button that expands the activity
320 * overflow list.
321 *
322 * <strong>Note:</strong> Clients would like to set this drawable
323 * as a clue about the action the chosen activity will perform. For
Svetoslav Ganov70853772011-09-30 19:57:35 -0700324 * example, if a share activity is to be chosen the drawable should
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700325 * give a clue that sharing is to be performed.
326 *
327 * @param drawable The drawable.
328 */
Mathew Inwood978c6e22018-08-21 15:58:55 +0100329 @UnsupportedAppUsage
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700330 public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700331 mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700332 }
333
334 /**
Svetoslav Ganov70853772011-09-30 19:57:35 -0700335 * Sets the content description for the button that expands the activity
336 * overflow list.
337 *
338 * description as a clue about the action performed by the button.
339 * For example, if a share activity is to be chosen the content
340 * description should be something like "Share with".
341 *
342 * @param resourceId The content description resource id.
343 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700344 public void setExpandActivityOverflowButtonContentDescription(@StringRes int resourceId) {
Svetoslav Ganov70853772011-09-30 19:57:35 -0700345 CharSequence contentDescription = mContext.getString(resourceId);
346 mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
347 }
348
349 /**
Adam Powell823f0742011-09-21 17:17:01 -0700350 * Set the provider hosting this view, if applicable.
351 * @hide Internal use only
352 */
353 public void setProvider(ActionProvider provider) {
354 mProvider = provider;
355 }
356
357 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700358 * Shows the popup window with activities.
359 *
360 * @return True if the popup was shown, false if already showing.
361 */
362 public boolean showPopup() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700363 if (isShowingPopup() || !mIsAttachedToWindow) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700364 return false;
365 }
366 mIsSelectingDefaultActivity = false;
367 showPopupUnchecked(mInitialActivityCount);
368 return true;
369 }
370
371 /**
372 * Shows the popup no matter if it was already showing.
373 *
374 * @param maxActivityCount The max number of activities to display.
375 */
376 private void showPopupUnchecked(int maxActivityCount) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700377 if (mAdapter.getDataModel() == null) {
378 throw new IllegalStateException("No data model. Did you call #setDataModel?");
379 }
380
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700381 getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
382
Svetoslav Ganovf632eaa2011-09-22 16:05:12 -0700383 final boolean defaultActivityButtonShown =
384 mDefaultActivityButton.getVisibility() == VISIBLE;
385
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700386 final int activityCount = mAdapter.getActivityCount();
Svetoslav Ganovf632eaa2011-09-22 16:05:12 -0700387 final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700388 if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
Svetoslav Ganovf632eaa2011-09-22 16:05:12 -0700389 && activityCount > maxActivityCount + maxActivityCountOffset) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700390 mAdapter.setShowFooterView(true);
Svetoslav Ganov1c07e222011-09-29 16:50:59 -0700391 mAdapter.setMaxActivityCount(maxActivityCount - 1);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700392 } else {
393 mAdapter.setShowFooterView(false);
Svetoslav Ganov1c07e222011-09-29 16:50:59 -0700394 mAdapter.setMaxActivityCount(maxActivityCount);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700395 }
396
397 ListPopupWindow popupWindow = getListPopupWindow();
398 if (!popupWindow.isShowing()) {
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700399 if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
400 mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700401 } else {
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700402 mAdapter.setShowDefaultActivity(false, false);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700403 }
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700404 final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
405 popupWindow.setContentWidth(contentWidth);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700406 popupWindow.show();
Adam Powell823f0742011-09-21 17:17:01 -0700407 if (mProvider != null) {
408 mProvider.subUiVisibilityChanged(true);
409 }
Svetoslav Ganov70853772011-09-30 19:57:35 -0700410 popupWindow.getListView().setContentDescription(mContext.getString(
411 R.string.activitychooserview_choose_application));
sergeyve471a4e2017-05-25 15:49:40 -0700412 popupWindow.getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700413 }
414 }
415
416 /**
417 * Dismisses the popup window with activities.
418 *
419 * @return True if dismissed, false if already dismissed.
420 */
421 public boolean dismissPopup() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700422 if (isShowingPopup()) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700423 getListPopupWindow().dismiss();
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700424 ViewTreeObserver viewTreeObserver = getViewTreeObserver();
425 if (viewTreeObserver.isAlive()) {
Svetoslav Ganovaa47a6b2012-01-24 13:55:35 -0800426 viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700427 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700428 }
429 return true;
430 }
431
432 /**
433 * Gets whether the popup window with activities is shown.
434 *
435 * @return True if the popup is shown.
436 */
437 public boolean isShowingPopup() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700438 return getListPopupWindow().isShowing();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700439 }
440
441 @Override
442 protected void onAttachedToWindow() {
443 super.onAttachedToWindow();
444 ActivityChooserModel dataModel = mAdapter.getDataModel();
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700445 if (dataModel != null) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700446 dataModel.registerObserver(mModelDataSetOberver);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700447 }
448 mIsAttachedToWindow = true;
449 }
450
451 @Override
452 protected void onDetachedFromWindow() {
453 super.onDetachedFromWindow();
454 ActivityChooserModel dataModel = mAdapter.getDataModel();
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700455 if (dataModel != null) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700456 dataModel.unregisterObserver(mModelDataSetOberver);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700457 }
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700458 ViewTreeObserver viewTreeObserver = getViewTreeObserver();
459 if (viewTreeObserver.isAlive()) {
Svetoslav Ganovaa47a6b2012-01-24 13:55:35 -0800460 viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700461 }
Svetoslav Ganov748ee2e2012-05-23 15:21:10 -0700462 if (isShowingPopup()) {
463 dismissPopup();
464 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700465 mIsAttachedToWindow = false;
466 }
467
468 @Override
469 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700470 View child = mActivityChooserContent;
471 // If the default action is not visible we want to be as tall as the
472 // ActionBar so if this widget is used in the latter it will look as
473 // a normal action button.
474 if (mDefaultActivityButton.getVisibility() != VISIBLE) {
475 heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
476 MeasureSpec.EXACTLY);
477 }
478 measureChild(child, widthMeasureSpec, heightMeasureSpec);
479 setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700480 }
481
482 @Override
483 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700484 mActivityChooserContent.layout(0, 0, right - left, bottom - top);
Svetoslav Ganov748ee2e2012-05-23 15:21:10 -0700485 if (!isShowingPopup()) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700486 dismissPopup();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700487 }
488 }
489
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700490 public ActivityChooserModel getDataModel() {
491 return mAdapter.getDataModel();
492 }
493
494 /**
495 * Sets a listener to receive a callback when the popup is dismissed.
496 *
497 * @param listener The listener to be notified.
498 */
499 public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
500 mOnDismissListener = listener;
501 }
502
503 /**
504 * Sets the initial count of items shown in the activities popup
505 * i.e. the items before the popup is expanded. This is an upper
506 * bound since it is not guaranteed that such number of intent
507 * handlers exist.
508 *
509 * @param itemCount The initial popup item count.
510 */
511 public void setInitialActivityCount(int itemCount) {
512 mInitialActivityCount = itemCount;
513 }
514
515 /**
Svetoslav Ganov70853772011-09-30 19:57:35 -0700516 * Sets a content description of the default action button. This
517 * resource should be a string taking one formatting argument and
518 * will be used for formatting the content description of the button
519 * dynamically as the default target changes. For example, a resource
520 * pointing to the string "share with %1$s" will result in a content
521 * description "share with Bluetooth" for the Bluetooth activity.
522 *
523 * @param resourceId The resource id.
524 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700525 public void setDefaultActionButtonContentDescription(@StringRes int resourceId) {
Svetoslav Ganov70853772011-09-30 19:57:35 -0700526 mDefaultActionButtonContentDescription = resourceId;
527 }
528
529 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700530 * Gets the list popup window which is lazily initialized.
531 *
532 * @return The popup.
533 */
534 private ListPopupWindow getListPopupWindow() {
535 if (mListPopupWindow == null) {
536 mListPopupWindow = new ListPopupWindow(getContext());
537 mListPopupWindow.setAdapter(mAdapter);
538 mListPopupWindow.setAnchorView(ActivityChooserView.this);
539 mListPopupWindow.setModal(true);
540 mListPopupWindow.setOnItemClickListener(mCallbacks);
541 mListPopupWindow.setOnDismissListener(mCallbacks);
542 }
543 return mListPopupWindow;
544 }
545
546 /**
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700547 * Updates the buttons state.
548 */
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700549 private void updateAppearance() {
550 // Expand overflow button.
551 if (mAdapter.getCount() > 0) {
552 mExpandActivityOverflowButton.setEnabled(true);
553 } else {
554 mExpandActivityOverflowButton.setEnabled(false);
555 }
556 // Default activity button.
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700557 final int activityCount = mAdapter.getActivityCount();
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700558 final int historySize = mAdapter.getHistorySize();
fredc5a1195f2012-05-15 09:03:56 -0700559 if (activityCount==1 || activityCount > 1 && historySize > 0) {
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700560 mDefaultActivityButton.setVisibility(VISIBLE);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700561 ResolveInfo activity = mAdapter.getDefaultActivity();
562 PackageManager packageManager = mContext.getPackageManager();
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700563 mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
Svetoslav Ganov70853772011-09-30 19:57:35 -0700564 if (mDefaultActionButtonContentDescription != 0) {
565 CharSequence label = activity.loadLabel(packageManager);
566 String contentDescription = mContext.getString(
567 mDefaultActionButtonContentDescription, label);
568 mDefaultActivityButton.setContentDescription(contentDescription);
569 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700570 } else {
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700571 mDefaultActivityButton.setVisibility(View.GONE);
572 }
573 // Activity chooser content.
574 if (mDefaultActivityButton.getVisibility() == VISIBLE) {
Svetoslavbaeabb62013-10-28 15:22:14 -0700575 mActivityChooserContent.setBackground(mActivityChooserContentBackground);
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700576 } else {
Svetoslavbaeabb62013-10-28 15:22:14 -0700577 mActivityChooserContent.setBackground(null);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700578 }
579 }
580
581 /**
582 * Interface implementation to avoid publishing them in the APIs.
583 */
584 private class Callbacks implements AdapterView.OnItemClickListener,
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700585 View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700586
587 // AdapterView#OnItemClickListener
588 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
589 ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter();
590 final int itemViewType = adapter.getItemViewType(position);
591 switch (itemViewType) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700592 case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: {
593 showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED);
594 } break;
595 case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: {
596 dismissPopup();
597 if (mIsSelectingDefaultActivity) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700598 // The item at position zero is the default already.
599 if (position > 0) {
600 mAdapter.getDataModel().setDefaultActivity(position);
601 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700602 } else {
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700603 // If the default target is not shown in the list, the first
604 // item in the model is default action => adjust index
605 position = mAdapter.getShowDefaultActivity() ? position : position + 1;
606 Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700607 if (launchIntent != null) {
Adam Powell314419c2012-01-24 13:33:09 -0800608 launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Svetoslavbaeabb62013-10-28 15:22:14 -0700609 ResolveInfo resolveInfo = mAdapter.getDataModel().getActivity(position);
610 startActivity(launchIntent, resolveInfo);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700611 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700612 }
613 } break;
614 default:
615 throw new IllegalArgumentException();
616 }
617 }
618
619 // View.OnClickListener
620 public void onClick(View view) {
Svetoslav Ganovbfbbcf52011-07-25 21:54:55 -0700621 if (view == mDefaultActivityButton) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700622 dismissPopup();
623 ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
624 final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
625 Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700626 if (launchIntent != null) {
Adam Powell314419c2012-01-24 13:33:09 -0800627 launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Svetoslavbaeabb62013-10-28 15:22:14 -0700628 startActivity(launchIntent, defaultActivity);
Svetoslav Ganov8c6c79f2011-07-29 20:14:09 -0700629 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700630 } else if (view == mExpandActivityOverflowButton) {
631 mIsSelectingDefaultActivity = false;
632 showPopupUnchecked(mInitialActivityCount);
633 } else {
634 throw new IllegalArgumentException();
635 }
636 }
637
638 // OnLongClickListener#onLongClick
639 @Override
640 public boolean onLongClick(View view) {
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700641 if (view == mDefaultActivityButton) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700642 if (mAdapter.getCount() > 0) {
643 mIsSelectingDefaultActivity = true;
644 showPopupUnchecked(mInitialActivityCount);
645 }
646 } else {
647 throw new IllegalArgumentException();
648 }
649 return true;
650 }
651
652 // PopUpWindow.OnDismissListener#onDismiss
653 public void onDismiss() {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700654 notifyOnDismissListener();
Adam Powell823f0742011-09-21 17:17:01 -0700655 if (mProvider != null) {
656 mProvider.subUiVisibilityChanged(false);
657 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700658 }
659
660 private void notifyOnDismissListener() {
661 if (mOnDismissListener != null) {
662 mOnDismissListener.onDismiss();
663 }
664 }
Svetoslavbaeabb62013-10-28 15:22:14 -0700665
666 private void startActivity(Intent intent, ResolveInfo resolveInfo) {
667 try {
668 mContext.startActivity(intent);
669 } catch (RuntimeException re) {
670 CharSequence appLabel = resolveInfo.loadLabel(mContext.getPackageManager());
671 String message = mContext.getString(
672 R.string.activitychooserview_choose_application_error, appLabel);
673 Log.e(LOG_TAG, message);
674 Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
675 }
676 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700677 }
678
679 /**
680 * Adapter for backing the list of activities shown in the popup.
681 */
682 private class ActivityChooserViewAdapter extends BaseAdapter {
683
684 public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE;
685
686 public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4;
687
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700688 private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700689
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700690 private static final int ITEM_VIEW_TYPE_FOOTER = 1;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700691
692 private static final int ITEM_VIEW_TYPE_COUNT = 3;
693
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700694 private ActivityChooserModel mDataModel;
695
696 private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
697
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700698 private boolean mShowDefaultActivity;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700699
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700700 private boolean mHighlightDefaultActivity;
701
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700702 private boolean mShowFooterView;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700703
704 public void setDataModel(ActivityChooserModel dataModel) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700705 ActivityChooserModel oldDataModel = mAdapter.getDataModel();
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700706 if (oldDataModel != null && isShown()) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700707 oldDataModel.unregisterObserver(mModelDataSetOberver);
708 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700709 mDataModel = dataModel;
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700710 if (dataModel != null && isShown()) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700711 dataModel.registerObserver(mModelDataSetOberver);
712 }
Svetoslav Ganovb9f286e2012-05-14 23:09:02 -0700713 notifyDataSetChanged();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700714 }
715
716 @Override
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700717 public int getItemViewType(int position) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700718 if (mShowFooterView && position == getCount() - 1) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700719 return ITEM_VIEW_TYPE_FOOTER;
720 } else {
721 return ITEM_VIEW_TYPE_ACTIVITY;
722 }
723 }
724
725 @Override
726 public int getViewTypeCount() {
727 return ITEM_VIEW_TYPE_COUNT;
728 }
729
730 public int getCount() {
731 int count = 0;
732 int activityCount = mDataModel.getActivityCount();
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700733 if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700734 activityCount--;
735 }
736 count = Math.min(activityCount, mMaxActivityCount);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700737 if (mShowFooterView) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700738 count++;
739 }
740 return count;
741 }
742
743 public Object getItem(int position) {
744 final int itemViewType = getItemViewType(position);
745 switch (itemViewType) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700746 case ITEM_VIEW_TYPE_FOOTER:
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700747 return null;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700748 case ITEM_VIEW_TYPE_ACTIVITY:
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700749 if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
750 position++;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700751 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700752 return mDataModel.getActivity(position);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700753 default:
754 throw new IllegalArgumentException();
755 }
756 }
757
758 public long getItemId(int position) {
759 return position;
760 }
761
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700762 public View getView(int position, View convertView, ViewGroup parent) {
763 final int itemViewType = getItemViewType(position);
764 switch (itemViewType) {
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700765 case ITEM_VIEW_TYPE_FOOTER:
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700766 if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) {
767 convertView = LayoutInflater.from(getContext()).inflate(
768 R.layout.activity_chooser_view_list_item, parent, false);
769 convertView.setId(ITEM_VIEW_TYPE_FOOTER);
Alan Viverette8e1a7292017-02-27 10:57:58 -0500770 TextView titleView = convertView.findViewById(R.id.title);
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700771 titleView.setText(mContext.getString(
772 R.string.activity_chooser_view_see_all));
773 }
774 return convertView;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700775 case ITEM_VIEW_TYPE_ACTIVITY:
776 if (convertView == null || convertView.getId() != R.id.list_item) {
777 convertView = LayoutInflater.from(getContext()).inflate(
778 R.layout.activity_chooser_view_list_item, parent, false);
779 }
780 PackageManager packageManager = mContext.getPackageManager();
781 // Set the icon
Alan Viverette8e1a7292017-02-27 10:57:58 -0500782 ImageView iconView = convertView.findViewById(R.id.icon);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700783 ResolveInfo activity = (ResolveInfo) getItem(position);
Svetoslav Ganov414051b2011-07-17 22:28:42 -0700784 iconView.setImageDrawable(activity.loadIcon(packageManager));
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700785 // Set the title.
Alan Viverette8e1a7292017-02-27 10:57:58 -0500786 TextView titleView = convertView.findViewById(R.id.title);
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700787 titleView.setText(activity.loadLabel(packageManager));
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700788 // Highlight the default.
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700789 if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700790 convertView.setActivated(true);
791 } else {
792 convertView.setActivated(false);
793 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700794 return convertView;
795 default:
796 throw new IllegalArgumentException();
797 }
798 }
799
800 public int measureContentWidth() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700801 // The user may have specified some of the target not to be shown but we
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700802 // want to measure all of them since after expansion they should fit.
803 final int oldMaxActivityCount = mMaxActivityCount;
804 mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED;
805
806 int contentWidth = 0;
807 View itemView = null;
808
809 final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
810 final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
811 final int count = getCount();
812
813 for (int i = 0; i < count; i++) {
814 itemView = getView(i, itemView, null);
815 itemView.measure(widthMeasureSpec, heightMeasureSpec);
816 contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth());
817 }
818
819 mMaxActivityCount = oldMaxActivityCount;
820
821 return contentWidth;
822 }
823
824 public void setMaxActivityCount(int maxActivityCount) {
825 if (mMaxActivityCount != maxActivityCount) {
826 mMaxActivityCount = maxActivityCount;
827 notifyDataSetChanged();
828 }
829 }
830
831 public ResolveInfo getDefaultActivity() {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700832 return mDataModel.getDefaultActivity();
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700833 }
834
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700835 public void setShowFooterView(boolean showFooterView) {
836 if (mShowFooterView != showFooterView) {
837 mShowFooterView = showFooterView;
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700838 notifyDataSetChanged();
839 }
840 }
841
842 public int getActivityCount() {
843 return mDataModel.getActivityCount();
844 }
845
Svetoslav Ganovf2e75402011-09-07 16:38:40 -0700846 public int getHistorySize() {
847 return mDataModel.getHistorySize();
848 }
849
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700850 public ActivityChooserModel getDataModel() {
851 return mDataModel;
852 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700853
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700854 public void setShowDefaultActivity(boolean showDefaultActivity,
855 boolean highlightDefaultActivity) {
856 if (mShowDefaultActivity != showDefaultActivity
857 || mHighlightDefaultActivity != highlightDefaultActivity) {
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700858 mShowDefaultActivity = showDefaultActivity;
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700859 mHighlightDefaultActivity = highlightDefaultActivity;
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700860 notifyDataSetChanged();
861 }
862 }
Svetoslav Ganovefab4e72011-09-20 14:35:39 -0700863
864 public boolean getShowDefaultActivity() {
865 return mShowDefaultActivity;
866 }
Svetoslav Ganov51ac0e92011-06-17 13:45:13 -0700867 }
868}