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.annotation.StringRes;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.database.DataSetObserver;
28 import android.graphics.Color;
29 import android.graphics.drawable.ColorDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.ActionProvider;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.ViewTreeObserver;
38 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
39 import android.view.accessibility.AccessibilityNodeInfo;
40 import android.widget.ActivityChooserModel.ActivityChooserModelClient;
41 
42 import com.android.internal.R;
43 import com.android.internal.view.menu.ShowableListMenu;
44 
45 /**
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>
65  *
66  * @hide
67  */
68 public class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient {
69 
70     private static final String LOG_TAG = "ActivityChooserView";
71 
72     /**
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     /**
88      * Stores the background drawable to allow hiding and latter showing.
89      */
90     private final Drawable mActivityChooserContentBackground;
91 
92     /**
93      * The expand activities action button;
94      */
95     private final FrameLayout mExpandActivityOverflowButton;
96 
97     /**
98      * The image for the expand activities action button;
99      */
100     private final ImageView mExpandActivityOverflowButtonImage;
101 
102     /**
103      * The default activities action button;
104      */
105     private final FrameLayout mDefaultActivityButton;
106 
107     /**
108      * The image for the default activities action button;
109      */
110     private final ImageView mDefaultActivityButtonImage;
111 
112     /**
113      * The maximal width of the list popup.
114      */
115     private final int mListPopupMaxWidth;
116 
117     /**
118      * The ActionProvider hosting this view, if applicable.
119      */
120     ActionProvider mProvider;
121 
122     /**
123      * Observer for the model data.
124      */
125     private final DataSetObserver mModelDataSetOberver = new DataSetObserver() {
126 
127         @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     };
138 
139     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();
147                     if (mProvider != null) {
148                         mProvider.subUiVisibilityChanged(true);
149                     }
150                 }
151             }
152         }
153     };
154 
155     /**
156      * Popup window for showing the activity overflow list.
157      */
158     private ListPopupWindow mListPopupWindow;
159 
160     /**
161      * 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     /**
181      * String resource for formatting content description of the default target.
182      */
183     private int mDefaultActionButtonContentDescription;
184 
185     /**
186      * Create a new instance.
187      *
188      * @param context The application environment.
189      */
ActivityChooserView(Context context)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      */
ActivityChooserView(Context context, AttributeSet attrs)200     public ActivityChooserView(Context context, AttributeSet attrs) {
201         this(context, attrs, 0);
202     }
203 
204     /**
205      * Create a new instance.
206      *
207      * @param context The application environment.
208      * @param attrs A collection of attributes.
209      * @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.
212      */
ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr)213     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      */
ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)230     public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
231         super(context, attrs, defStyleAttr, defStyleRes);
232 
233         TypedArray attributesArray = context.obtainStyledAttributes(attrs,
234                 R.styleable.ActivityChooserView, defStyleAttr, defStyleRes);
235         saveAttributeDataForStyleable(context, R.styleable.ActivityChooserView, attrs,
236                 attributesArray, defStyleAttr, defStyleRes);
237 
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 
245         attributesArray.recycle();
246 
247         LayoutInflater inflater = LayoutInflater.from(mContext);
248         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);
253         mActivityChooserContentBackground = mActivityChooserContent.getBackground();
254 
255         mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button);
256         mDefaultActivityButton.setOnClickListener(mCallbacks);
257         mDefaultActivityButton.setOnLongClickListener(mCallbacks);
258         mDefaultActivityButtonImage = mDefaultActivityButton.findViewById(R.id.image);
259 
260         final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
261         expandButton.setOnClickListener(mCallbacks);
262         expandButton.setAccessibilityDelegate(new AccessibilityDelegate() {
263             @Override
264             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
265                 super.onInitializeAccessibilityNodeInfo(host, info);
266                 info.setCanOpenPopup(true);
267             }
268         });
269         expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
270             @Override
271             public ShowableListMenu getPopup() {
272                 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 
289         mExpandActivityOverflowButtonImage =
290             expandButton.findViewById(R.id.image);
291         mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
292 
293         mAdapter = new ActivityChooserViewAdapter();
294         mAdapter.registerDataSetObserver(new DataSetObserver() {
295             @Override
296             public void onChanged() {
297                 super.onChanged();
298                 updateAppearance();
299             }
300         });
301 
302         Resources resources = context.getResources();
303         mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
304               resources.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
305     }
306 
307     /**
308      * {@inheritDoc}
309      */
setActivityChooserModel(ActivityChooserModel dataModel)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
324      * example, if a share activity is to be chosen the drawable should
325      * give a clue that sharing is to be performed.
326      *
327      * @param drawable The drawable.
328      */
329     @UnsupportedAppUsage
setExpandActivityOverflowButtonDrawable(Drawable drawable)330     public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
331         mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
332     }
333 
334     /**
335      * 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      */
setExpandActivityOverflowButtonContentDescription(@tringRes int resourceId)344     public void setExpandActivityOverflowButtonContentDescription(@StringRes int resourceId) {
345         CharSequence contentDescription = mContext.getString(resourceId);
346         mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
347     }
348 
349     /**
350      * Set the provider hosting this view, if applicable.
351      * @hide Internal use only
352      */
setProvider(ActionProvider provider)353     public void setProvider(ActionProvider provider) {
354         mProvider = provider;
355     }
356 
357     /**
358      * Shows the popup window with activities.
359      *
360      * @return True if the popup was shown, false if already showing.
361      */
showPopup()362     public boolean showPopup() {
363         if (isShowingPopup() || !mIsAttachedToWindow) {
364             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      */
showPopupUnchecked(int maxActivityCount)376     private void showPopupUnchecked(int maxActivityCount) {
377         if (mAdapter.getDataModel() == null) {
378             throw new IllegalStateException("No data model. Did you call #setDataModel?");
379         }
380 
381         getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
382 
383         final boolean defaultActivityButtonShown =
384             mDefaultActivityButton.getVisibility() == VISIBLE;
385 
386         final int activityCount = mAdapter.getActivityCount();
387         final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
388         if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
389                 && activityCount > maxActivityCount + maxActivityCountOffset) {
390             mAdapter.setShowFooterView(true);
391             mAdapter.setMaxActivityCount(maxActivityCount - 1);
392         } else {
393             mAdapter.setShowFooterView(false);
394             mAdapter.setMaxActivityCount(maxActivityCount);
395         }
396 
397         ListPopupWindow popupWindow = getListPopupWindow();
398         if (!popupWindow.isShowing()) {
399             if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
400                 mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
401             } else {
402                 mAdapter.setShowDefaultActivity(false, false);
403             }
404             final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
405             popupWindow.setContentWidth(contentWidth);
406             popupWindow.show();
407             if (mProvider != null) {
408                 mProvider.subUiVisibilityChanged(true);
409             }
410             popupWindow.getListView().setContentDescription(mContext.getString(
411                     R.string.activitychooserview_choose_application));
412             popupWindow.getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
413         }
414     }
415 
416     /**
417      * Dismisses the popup window with activities.
418      *
419      * @return True if dismissed, false if already dismissed.
420      */
dismissPopup()421     public boolean dismissPopup() {
422         if (isShowingPopup()) {
423             getListPopupWindow().dismiss();
424             ViewTreeObserver viewTreeObserver = getViewTreeObserver();
425             if (viewTreeObserver.isAlive()) {
426                 viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
427             }
428         }
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      */
isShowingPopup()437     public boolean isShowingPopup() {
438         return getListPopupWindow().isShowing();
439     }
440 
441     @Override
onAttachedToWindow()442     protected void onAttachedToWindow() {
443         super.onAttachedToWindow();
444         ActivityChooserModel dataModel = mAdapter.getDataModel();
445         if (dataModel != null) {
446             dataModel.registerObserver(mModelDataSetOberver);
447         }
448         mIsAttachedToWindow = true;
449     }
450 
451     @Override
onDetachedFromWindow()452     protected void onDetachedFromWindow() {
453         super.onDetachedFromWindow();
454         ActivityChooserModel dataModel = mAdapter.getDataModel();
455         if (dataModel != null) {
456             dataModel.unregisterObserver(mModelDataSetOberver);
457         }
458         ViewTreeObserver viewTreeObserver = getViewTreeObserver();
459         if (viewTreeObserver.isAlive()) {
460             viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
461         }
462         if (isShowingPopup()) {
463             dismissPopup();
464         }
465         mIsAttachedToWindow = false;
466     }
467 
468     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)469     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
470         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());
480     }
481 
482     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)483     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
484         mActivityChooserContent.layout(0, 0, right - left, bottom - top);
485         if (!isShowingPopup()) {
486             dismissPopup();
487         }
488     }
489 
getDataModel()490     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      */
setOnDismissListener(PopupWindow.OnDismissListener listener)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      */
setInitialActivityCount(int itemCount)511     public void setInitialActivityCount(int itemCount) {
512         mInitialActivityCount = itemCount;
513     }
514 
515     /**
516      * 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      */
setDefaultActionButtonContentDescription(@tringRes int resourceId)525     public void setDefaultActionButtonContentDescription(@StringRes int resourceId) {
526         mDefaultActionButtonContentDescription = resourceId;
527     }
528 
529     /**
530      * Gets the list popup window which is lazily initialized.
531      *
532      * @return The popup.
533      */
getListPopupWindow()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     /**
547      * Updates the buttons state.
548      */
updateAppearance()549     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.
557         final int activityCount = mAdapter.getActivityCount();
558         final int historySize = mAdapter.getHistorySize();
559         if (activityCount==1 || activityCount > 1 && historySize > 0) {
560             mDefaultActivityButton.setVisibility(VISIBLE);
561             ResolveInfo activity = mAdapter.getDefaultActivity();
562             PackageManager packageManager = mContext.getPackageManager();
563             mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
564             if (mDefaultActionButtonContentDescription != 0) {
565                 CharSequence label = activity.loadLabel(packageManager);
566                 String contentDescription = mContext.getString(
567                         mDefaultActionButtonContentDescription, label);
568                 mDefaultActivityButton.setContentDescription(contentDescription);
569             }
570         } else {
571             mDefaultActivityButton.setVisibility(View.GONE);
572         }
573         // Activity chooser content.
574         if (mDefaultActivityButton.getVisibility() == VISIBLE) {
575             mActivityChooserContent.setBackground(mActivityChooserContentBackground);
576         } else {
577             mActivityChooserContent.setBackground(null);
578         }
579     }
580 
581     /**
582      * Interface implementation to avoid publishing them in the APIs.
583      */
584     private class Callbacks implements AdapterView.OnItemClickListener,
585             View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
586 
587         // AdapterView#OnItemClickListener
onItemClick(AdapterView<?> parent, View view, int position, long id)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) {
592                 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) {
598                         // The item at position zero is the default already.
599                         if (position > 0) {
600                             mAdapter.getDataModel().setDefaultActivity(position);
601                         }
602                     } else {
603                         // 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);
607                         if (launchIntent != null) {
608                             launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
609                             ResolveInfo resolveInfo = mAdapter.getDataModel().getActivity(position);
610                             startActivity(launchIntent, resolveInfo);
611                         }
612                     }
613                 } break;
614                 default:
615                     throw new IllegalArgumentException();
616             }
617         }
618 
619         // View.OnClickListener
onClick(View view)620         public void onClick(View view) {
621             if (view == mDefaultActivityButton) {
622                 dismissPopup();
623                 ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
624                 final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
625                 Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
626                 if (launchIntent != null) {
627                     launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
628                     startActivity(launchIntent, defaultActivity);
629                 }
630             } else if (view == mExpandActivityOverflowButton) {
631                 mIsSelectingDefaultActivity = false;
632                 showPopupUnchecked(mInitialActivityCount);
633             } else {
634                 throw new IllegalArgumentException();
635             }
636         }
637 
638         // OnLongClickListener#onLongClick
639         @Override
onLongClick(View view)640         public boolean onLongClick(View view) {
641             if (view == mDefaultActivityButton) {
642                 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
onDismiss()653         public void onDismiss() {
654             notifyOnDismissListener();
655             if (mProvider != null) {
656                 mProvider.subUiVisibilityChanged(false);
657             }
658         }
659 
notifyOnDismissListener()660         private void notifyOnDismissListener() {
661             if (mOnDismissListener != null) {
662                 mOnDismissListener.onDismiss();
663             }
664         }
665 
startActivity(Intent intent, ResolveInfo resolveInfo)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         }
677     }
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 
688         private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
689 
690         private static final int ITEM_VIEW_TYPE_FOOTER = 1;
691 
692         private static final int ITEM_VIEW_TYPE_COUNT = 3;
693 
694         private ActivityChooserModel mDataModel;
695 
696         private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
697 
698         private boolean mShowDefaultActivity;
699 
700         private boolean mHighlightDefaultActivity;
701 
702         private boolean mShowFooterView;
703 
setDataModel(ActivityChooserModel dataModel)704         public void setDataModel(ActivityChooserModel dataModel) {
705             ActivityChooserModel oldDataModel = mAdapter.getDataModel();
706             if (oldDataModel != null && isShown()) {
707                 oldDataModel.unregisterObserver(mModelDataSetOberver);
708             }
709             mDataModel = dataModel;
710             if (dataModel != null && isShown()) {
711                 dataModel.registerObserver(mModelDataSetOberver);
712             }
713             notifyDataSetChanged();
714         }
715 
716         @Override
getItemViewType(int position)717         public int getItemViewType(int position) {
718             if (mShowFooterView && position == getCount() - 1) {
719                 return ITEM_VIEW_TYPE_FOOTER;
720             } else {
721                 return ITEM_VIEW_TYPE_ACTIVITY;
722             }
723         }
724 
725         @Override
getViewTypeCount()726         public int getViewTypeCount() {
727             return ITEM_VIEW_TYPE_COUNT;
728         }
729 
getCount()730         public int getCount() {
731             int count = 0;
732             int activityCount = mDataModel.getActivityCount();
733             if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
734                 activityCount--;
735             }
736             count = Math.min(activityCount, mMaxActivityCount);
737             if (mShowFooterView) {
738                 count++;
739             }
740             return count;
741         }
742 
getItem(int position)743         public Object getItem(int position) {
744             final int itemViewType = getItemViewType(position);
745             switch (itemViewType) {
746                 case ITEM_VIEW_TYPE_FOOTER:
747                     return null;
748                 case ITEM_VIEW_TYPE_ACTIVITY:
749                     if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
750                         position++;
751                     }
752                     return mDataModel.getActivity(position);
753                 default:
754                     throw new IllegalArgumentException();
755             }
756         }
757 
getItemId(int position)758         public long getItemId(int position) {
759             return position;
760         }
761 
getView(int position, View convertView, ViewGroup parent)762         public View getView(int position, View convertView, ViewGroup parent) {
763             final int itemViewType = getItemViewType(position);
764             switch (itemViewType) {
765                 case ITEM_VIEW_TYPE_FOOTER:
766                     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);
770                         TextView titleView = convertView.findViewById(R.id.title);
771                         titleView.setText(mContext.getString(
772                                 R.string.activity_chooser_view_see_all));
773                     }
774                     return convertView;
775                 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
782                     ImageView iconView = convertView.findViewById(R.id.icon);
783                     ResolveInfo activity = (ResolveInfo) getItem(position);
784                     iconView.setImageDrawable(activity.loadIcon(packageManager));
785                     // Set the title.
786                     TextView titleView = convertView.findViewById(R.id.title);
787                     titleView.setText(activity.loadLabel(packageManager));
788                     // Highlight the default.
789                     if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
790                         convertView.setActivated(true);
791                     } else {
792                         convertView.setActivated(false);
793                     }
794                     return convertView;
795                 default:
796                     throw new IllegalArgumentException();
797             }
798         }
799 
measureContentWidth()800         public int measureContentWidth() {
801             // The user may have specified some of the target not to be shown but we
802             // 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 
setMaxActivityCount(int maxActivityCount)824         public void setMaxActivityCount(int maxActivityCount) {
825             if (mMaxActivityCount != maxActivityCount) {
826                 mMaxActivityCount = maxActivityCount;
827                 notifyDataSetChanged();
828             }
829         }
830 
getDefaultActivity()831         public ResolveInfo getDefaultActivity() {
832             return mDataModel.getDefaultActivity();
833         }
834 
setShowFooterView(boolean showFooterView)835         public void setShowFooterView(boolean showFooterView) {
836             if (mShowFooterView != showFooterView) {
837                 mShowFooterView = showFooterView;
838                 notifyDataSetChanged();
839             }
840         }
841 
getActivityCount()842         public int getActivityCount() {
843             return mDataModel.getActivityCount();
844         }
845 
getHistorySize()846         public int getHistorySize() {
847             return mDataModel.getHistorySize();
848         }
849 
getDataModel()850         public ActivityChooserModel getDataModel() {
851             return mDataModel;
852         }
853 
setShowDefaultActivity(boolean showDefaultActivity, boolean highlightDefaultActivity)854         public void setShowDefaultActivity(boolean showDefaultActivity,
855                 boolean highlightDefaultActivity) {
856             if (mShowDefaultActivity != showDefaultActivity
857                     || mHighlightDefaultActivity != highlightDefaultActivity) {
858                 mShowDefaultActivity = showDefaultActivity;
859                 mHighlightDefaultActivity = highlightDefaultActivity;
860                 notifyDataSetChanged();
861             }
862         }
863 
getShowDefaultActivity()864         public boolean getShowDefaultActivity() {
865             return mShowDefaultActivity;
866         }
867     }
868 }
869