• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.content.res.Resources;
25 import android.content.res.TypedArray;
26 import android.database.DataSetObserver;
27 import android.graphics.Color;
28 import android.graphics.drawable.ColorDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.ActionProvider;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ViewTreeObserver;
37 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
38 import android.view.accessibility.AccessibilityNodeInfo;
39 import android.widget.ActivityChooserModel.ActivityChooserModelClient;
40 
41 import com.android.internal.R;
42 import com.android.internal.view.menu.ShowableListMenu;
43 
44 /**
45  * This class is a view for choosing an activity for handling a given {@link Intent}.
46  * <p>
47  * The view is composed of two adjacent buttons:
48  * <ul>
49  * <li>
50  * The left button is an immediate action and allows one click activity choosing.
51  * Tapping this button immediately executes the intent without requiring any further
52  * user input. Long press on this button shows a popup for changing the default
53  * activity.
54  * </li>
55  * <li>
56  * The right button is an overflow action and provides an optimized menu
57  * of additional activities. Tapping this button shows a popup anchored to this
58  * view, listing the most frequently used activities. This list is initially
59  * limited to a small number of items in frequency used order. The last item,
60  * "Show all..." serves as an affordance to display all available activities.
61  * </li>
62  * </ul>
63  * </p>
64  *
65  * @hide
66  */
67 public class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient {
68 
69     private static final String LOG_TAG = "ActivityChooserView";
70 
71     /**
72      * An adapter for displaying the activities in an {@link AdapterView}.
73      */
74     private final ActivityChooserViewAdapter mAdapter;
75 
76     /**
77      * Implementation of various interfaces to avoid publishing them in the APIs.
78      */
79     private final Callbacks mCallbacks;
80 
81     /**
82      * The content of this view.
83      */
84     private final LinearLayout mActivityChooserContent;
85 
86     /**
87      * Stores the background drawable to allow hiding and latter showing.
88      */
89     private final Drawable mActivityChooserContentBackground;
90 
91     /**
92      * The expand activities action button;
93      */
94     private final FrameLayout mExpandActivityOverflowButton;
95 
96     /**
97      * The image for the expand activities action button;
98      */
99     private final ImageView mExpandActivityOverflowButtonImage;
100 
101     /**
102      * The default activities action button;
103      */
104     private final FrameLayout mDefaultActivityButton;
105 
106     /**
107      * The image for the default activities action button;
108      */
109     private final ImageView mDefaultActivityButtonImage;
110 
111     /**
112      * The maximal width of the list popup.
113      */
114     private final int mListPopupMaxWidth;
115 
116     /**
117      * The ActionProvider hosting this view, if applicable.
118      */
119     ActionProvider mProvider;
120 
121     /**
122      * Observer for the model data.
123      */
124     private final DataSetObserver mModelDataSetOberver = new DataSetObserver() {
125 
126         @Override
127         public void onChanged() {
128             super.onChanged();
129             mAdapter.notifyDataSetChanged();
130         }
131         @Override
132         public void onInvalidated() {
133             super.onInvalidated();
134             mAdapter.notifyDataSetInvalidated();
135         }
136     };
137 
138     private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() {
139         @Override
140         public void onGlobalLayout() {
141             if (isShowingPopup()) {
142                 if (!isShown()) {
143                     getListPopupWindow().dismiss();
144                 } else {
145                     getListPopupWindow().show();
146                     if (mProvider != null) {
147                         mProvider.subUiVisibilityChanged(true);
148                     }
149                 }
150             }
151         }
152     };
153 
154     /**
155      * Popup window for showing the activity overflow list.
156      */
157     private ListPopupWindow mListPopupWindow;
158 
159     /**
160      * Listener for the dismissal of the popup/alert.
161      */
162     private PopupWindow.OnDismissListener mOnDismissListener;
163 
164     /**
165      * Flag whether a default activity currently being selected.
166      */
167     private boolean mIsSelectingDefaultActivity;
168 
169     /**
170      * The count of activities in the popup.
171      */
172     private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT;
173 
174     /**
175      * Flag whether this view is attached to a window.
176      */
177     private boolean mIsAttachedToWindow;
178 
179     /**
180      * String resource for formatting content description of the default target.
181      */
182     private int mDefaultActionButtonContentDescription;
183 
184     /**
185      * Create a new instance.
186      *
187      * @param context The application environment.
188      */
ActivityChooserView(Context context)189     public ActivityChooserView(Context context) {
190         this(context, null);
191     }
192 
193     /**
194      * Create a new instance.
195      *
196      * @param context The application environment.
197      * @param attrs A collection of attributes.
198      */
ActivityChooserView(Context context, AttributeSet attrs)199     public ActivityChooserView(Context context, AttributeSet attrs) {
200         this(context, attrs, 0);
201     }
202 
203     /**
204      * Create a new instance.
205      *
206      * @param context The application environment.
207      * @param attrs A collection of attributes.
208      * @param defStyleAttr An attribute in the current theme that contains a
209      *        reference to a style resource that supplies default values for
210      *        the view. Can be 0 to not look for defaults.
211      */
ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr)212     public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) {
213         this(context, attrs, defStyleAttr, 0);
214     }
215 
216     /**
217      * Create a new instance.
218      *
219      * @param context The application environment.
220      * @param attrs A collection of attributes.
221      * @param defStyleAttr An attribute in the current theme that contains a
222      *        reference to a style resource that supplies default values for
223      *        the view. Can be 0 to not look for defaults.
224      * @param defStyleRes A resource identifier of a style resource that
225      *        supplies default values for the view, used only if
226      *        defStyleAttr is 0 or can not be found in the theme. Can be 0
227      *        to not look for defaults.
228      */
ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)229     public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
230         super(context, attrs, defStyleAttr, defStyleRes);
231 
232         TypedArray attributesArray = context.obtainStyledAttributes(attrs,
233                 R.styleable.ActivityChooserView, defStyleAttr, defStyleRes);
234 
235         mInitialActivityCount = attributesArray.getInt(
236                 R.styleable.ActivityChooserView_initialActivityCount,
237                 ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT);
238 
239         Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable(
240                 R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable);
241 
242         attributesArray.recycle();
243 
244         LayoutInflater inflater = LayoutInflater.from(mContext);
245         inflater.inflate(R.layout.activity_chooser_view, this, true);
246 
247         mCallbacks = new Callbacks();
248 
249         mActivityChooserContent = (LinearLayout) findViewById(R.id.activity_chooser_view_content);
250         mActivityChooserContentBackground = mActivityChooserContent.getBackground();
251 
252         mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button);
253         mDefaultActivityButton.setOnClickListener(mCallbacks);
254         mDefaultActivityButton.setOnLongClickListener(mCallbacks);
255         mDefaultActivityButtonImage = mDefaultActivityButton.findViewById(R.id.image);
256 
257         final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
258         expandButton.setOnClickListener(mCallbacks);
259         expandButton.setAccessibilityDelegate(new AccessibilityDelegate() {
260             @Override
261             public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
262                 super.onInitializeAccessibilityNodeInfo(host, info);
263                 info.setCanOpenPopup(true);
264             }
265         });
266         expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
267             @Override
268             public ShowableListMenu getPopup() {
269                 return getListPopupWindow();
270             }
271 
272             @Override
273             protected boolean onForwardingStarted() {
274                 showPopup();
275                 return true;
276             }
277 
278             @Override
279             protected boolean onForwardingStopped() {
280                 dismissPopup();
281                 return true;
282             }
283         });
284         mExpandActivityOverflowButton = expandButton;
285 
286         mExpandActivityOverflowButtonImage =
287             expandButton.findViewById(R.id.image);
288         mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
289 
290         mAdapter = new ActivityChooserViewAdapter();
291         mAdapter.registerDataSetObserver(new DataSetObserver() {
292             @Override
293             public void onChanged() {
294                 super.onChanged();
295                 updateAppearance();
296             }
297         });
298 
299         Resources resources = context.getResources();
300         mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
301               resources.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
302     }
303 
304     /**
305      * {@inheritDoc}
306      */
setActivityChooserModel(ActivityChooserModel dataModel)307     public void setActivityChooserModel(ActivityChooserModel dataModel) {
308         mAdapter.setDataModel(dataModel);
309         if (isShowingPopup()) {
310             dismissPopup();
311             showPopup();
312         }
313     }
314 
315     /**
316      * Sets the background for the button that expands the activity
317      * overflow list.
318      *
319      * <strong>Note:</strong> Clients would like to set this drawable
320      * as a clue about the action the chosen activity will perform. For
321      * example, if a share activity is to be chosen the drawable should
322      * give a clue that sharing is to be performed.
323      *
324      * @param drawable The drawable.
325      */
setExpandActivityOverflowButtonDrawable(Drawable drawable)326     public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
327         mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
328     }
329 
330     /**
331      * Sets the content description for the button that expands the activity
332      * overflow list.
333      *
334      * description as a clue about the action performed by the button.
335      * For example, if a share activity is to be chosen the content
336      * description should be something like "Share with".
337      *
338      * @param resourceId The content description resource id.
339      */
setExpandActivityOverflowButtonContentDescription(@tringRes int resourceId)340     public void setExpandActivityOverflowButtonContentDescription(@StringRes int resourceId) {
341         CharSequence contentDescription = mContext.getString(resourceId);
342         mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
343     }
344 
345     /**
346      * Set the provider hosting this view, if applicable.
347      * @hide Internal use only
348      */
setProvider(ActionProvider provider)349     public void setProvider(ActionProvider provider) {
350         mProvider = provider;
351     }
352 
353     /**
354      * Shows the popup window with activities.
355      *
356      * @return True if the popup was shown, false if already showing.
357      */
showPopup()358     public boolean showPopup() {
359         if (isShowingPopup() || !mIsAttachedToWindow) {
360             return false;
361         }
362         mIsSelectingDefaultActivity = false;
363         showPopupUnchecked(mInitialActivityCount);
364         return true;
365     }
366 
367     /**
368      * Shows the popup no matter if it was already showing.
369      *
370      * @param maxActivityCount The max number of activities to display.
371      */
showPopupUnchecked(int maxActivityCount)372     private void showPopupUnchecked(int maxActivityCount) {
373         if (mAdapter.getDataModel() == null) {
374             throw new IllegalStateException("No data model. Did you call #setDataModel?");
375         }
376 
377         getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
378 
379         final boolean defaultActivityButtonShown =
380             mDefaultActivityButton.getVisibility() == VISIBLE;
381 
382         final int activityCount = mAdapter.getActivityCount();
383         final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
384         if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
385                 && activityCount > maxActivityCount + maxActivityCountOffset) {
386             mAdapter.setShowFooterView(true);
387             mAdapter.setMaxActivityCount(maxActivityCount - 1);
388         } else {
389             mAdapter.setShowFooterView(false);
390             mAdapter.setMaxActivityCount(maxActivityCount);
391         }
392 
393         ListPopupWindow popupWindow = getListPopupWindow();
394         if (!popupWindow.isShowing()) {
395             if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
396                 mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
397             } else {
398                 mAdapter.setShowDefaultActivity(false, false);
399             }
400             final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
401             popupWindow.setContentWidth(contentWidth);
402             popupWindow.show();
403             if (mProvider != null) {
404                 mProvider.subUiVisibilityChanged(true);
405             }
406             popupWindow.getListView().setContentDescription(mContext.getString(
407                     R.string.activitychooserview_choose_application));
408             popupWindow.getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
409         }
410     }
411 
412     /**
413      * Dismisses the popup window with activities.
414      *
415      * @return True if dismissed, false if already dismissed.
416      */
dismissPopup()417     public boolean dismissPopup() {
418         if (isShowingPopup()) {
419             getListPopupWindow().dismiss();
420             ViewTreeObserver viewTreeObserver = getViewTreeObserver();
421             if (viewTreeObserver.isAlive()) {
422                 viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
423             }
424         }
425         return true;
426     }
427 
428     /**
429      * Gets whether the popup window with activities is shown.
430      *
431      * @return True if the popup is shown.
432      */
isShowingPopup()433     public boolean isShowingPopup() {
434         return getListPopupWindow().isShowing();
435     }
436 
437     @Override
onAttachedToWindow()438     protected void onAttachedToWindow() {
439         super.onAttachedToWindow();
440         ActivityChooserModel dataModel = mAdapter.getDataModel();
441         if (dataModel != null) {
442             dataModel.registerObserver(mModelDataSetOberver);
443         }
444         mIsAttachedToWindow = true;
445     }
446 
447     @Override
onDetachedFromWindow()448     protected void onDetachedFromWindow() {
449         super.onDetachedFromWindow();
450         ActivityChooserModel dataModel = mAdapter.getDataModel();
451         if (dataModel != null) {
452             dataModel.unregisterObserver(mModelDataSetOberver);
453         }
454         ViewTreeObserver viewTreeObserver = getViewTreeObserver();
455         if (viewTreeObserver.isAlive()) {
456             viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
457         }
458         if (isShowingPopup()) {
459             dismissPopup();
460         }
461         mIsAttachedToWindow = false;
462     }
463 
464     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)465     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
466         View child = mActivityChooserContent;
467         // If the default action is not visible we want to be as tall as the
468         // ActionBar so if this widget is used in the latter it will look as
469         // a normal action button.
470         if (mDefaultActivityButton.getVisibility() != VISIBLE) {
471             heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
472                     MeasureSpec.EXACTLY);
473         }
474         measureChild(child, widthMeasureSpec, heightMeasureSpec);
475         setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
476     }
477 
478     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)479     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
480         mActivityChooserContent.layout(0, 0, right - left, bottom - top);
481         if (!isShowingPopup()) {
482             dismissPopup();
483         }
484     }
485 
getDataModel()486     public ActivityChooserModel getDataModel() {
487         return mAdapter.getDataModel();
488     }
489 
490     /**
491      * Sets a listener to receive a callback when the popup is dismissed.
492      *
493      * @param listener The listener to be notified.
494      */
setOnDismissListener(PopupWindow.OnDismissListener listener)495     public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
496         mOnDismissListener = listener;
497     }
498 
499     /**
500      * Sets the initial count of items shown in the activities popup
501      * i.e. the items before the popup is expanded. This is an upper
502      * bound since it is not guaranteed that such number of intent
503      * handlers exist.
504      *
505      * @param itemCount The initial popup item count.
506      */
setInitialActivityCount(int itemCount)507     public void setInitialActivityCount(int itemCount) {
508         mInitialActivityCount = itemCount;
509     }
510 
511     /**
512      * Sets a content description of the default action button. This
513      * resource should be a string taking one formatting argument and
514      * will be used for formatting the content description of the button
515      * dynamically as the default target changes. For example, a resource
516      * pointing to the string "share with %1$s" will result in a content
517      * description "share with Bluetooth" for the Bluetooth activity.
518      *
519      * @param resourceId The resource id.
520      */
setDefaultActionButtonContentDescription(@tringRes int resourceId)521     public void setDefaultActionButtonContentDescription(@StringRes int resourceId) {
522         mDefaultActionButtonContentDescription = resourceId;
523     }
524 
525     /**
526      * Gets the list popup window which is lazily initialized.
527      *
528      * @return The popup.
529      */
getListPopupWindow()530     private ListPopupWindow getListPopupWindow() {
531         if (mListPopupWindow == null) {
532             mListPopupWindow = new ListPopupWindow(getContext());
533             mListPopupWindow.setAdapter(mAdapter);
534             mListPopupWindow.setAnchorView(ActivityChooserView.this);
535             mListPopupWindow.setModal(true);
536             mListPopupWindow.setOnItemClickListener(mCallbacks);
537             mListPopupWindow.setOnDismissListener(mCallbacks);
538         }
539         return mListPopupWindow;
540     }
541 
542     /**
543      * Updates the buttons state.
544      */
updateAppearance()545     private void updateAppearance() {
546         // Expand overflow button.
547         if (mAdapter.getCount() > 0) {
548             mExpandActivityOverflowButton.setEnabled(true);
549         } else {
550             mExpandActivityOverflowButton.setEnabled(false);
551         }
552         // Default activity button.
553         final int activityCount = mAdapter.getActivityCount();
554         final int historySize = mAdapter.getHistorySize();
555         if (activityCount==1 || activityCount > 1 && historySize > 0) {
556             mDefaultActivityButton.setVisibility(VISIBLE);
557             ResolveInfo activity = mAdapter.getDefaultActivity();
558             PackageManager packageManager = mContext.getPackageManager();
559             mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
560             if (mDefaultActionButtonContentDescription != 0) {
561                 CharSequence label = activity.loadLabel(packageManager);
562                 String contentDescription = mContext.getString(
563                         mDefaultActionButtonContentDescription, label);
564                 mDefaultActivityButton.setContentDescription(contentDescription);
565             }
566         } else {
567             mDefaultActivityButton.setVisibility(View.GONE);
568         }
569         // Activity chooser content.
570         if (mDefaultActivityButton.getVisibility() == VISIBLE) {
571             mActivityChooserContent.setBackground(mActivityChooserContentBackground);
572         } else {
573             mActivityChooserContent.setBackground(null);
574         }
575     }
576 
577     /**
578      * Interface implementation to avoid publishing them in the APIs.
579      */
580     private class Callbacks implements AdapterView.OnItemClickListener,
581             View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
582 
583         // AdapterView#OnItemClickListener
onItemClick(AdapterView<?> parent, View view, int position, long id)584         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
585             ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter();
586             final int itemViewType = adapter.getItemViewType(position);
587             switch (itemViewType) {
588                 case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: {
589                     showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED);
590                 } break;
591                 case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: {
592                     dismissPopup();
593                     if (mIsSelectingDefaultActivity) {
594                         // The item at position zero is the default already.
595                         if (position > 0) {
596                             mAdapter.getDataModel().setDefaultActivity(position);
597                         }
598                     } else {
599                         // If the default target is not shown in the list, the first
600                         // item in the model is default action => adjust index
601                         position = mAdapter.getShowDefaultActivity() ? position : position + 1;
602                         Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
603                         if (launchIntent != null) {
604                             launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
605                             ResolveInfo resolveInfo = mAdapter.getDataModel().getActivity(position);
606                             startActivity(launchIntent, resolveInfo);
607                         }
608                     }
609                 } break;
610                 default:
611                     throw new IllegalArgumentException();
612             }
613         }
614 
615         // View.OnClickListener
onClick(View view)616         public void onClick(View view) {
617             if (view == mDefaultActivityButton) {
618                 dismissPopup();
619                 ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
620                 final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
621                 Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
622                 if (launchIntent != null) {
623                     launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
624                     startActivity(launchIntent, defaultActivity);
625                 }
626             } else if (view == mExpandActivityOverflowButton) {
627                 mIsSelectingDefaultActivity = false;
628                 showPopupUnchecked(mInitialActivityCount);
629             } else {
630                 throw new IllegalArgumentException();
631             }
632         }
633 
634         // OnLongClickListener#onLongClick
635         @Override
onLongClick(View view)636         public boolean onLongClick(View view) {
637             if (view == mDefaultActivityButton) {
638                 if (mAdapter.getCount() > 0) {
639                     mIsSelectingDefaultActivity = true;
640                     showPopupUnchecked(mInitialActivityCount);
641                 }
642             } else {
643                 throw new IllegalArgumentException();
644             }
645             return true;
646         }
647 
648         // PopUpWindow.OnDismissListener#onDismiss
onDismiss()649         public void onDismiss() {
650             notifyOnDismissListener();
651             if (mProvider != null) {
652                 mProvider.subUiVisibilityChanged(false);
653             }
654         }
655 
notifyOnDismissListener()656         private void notifyOnDismissListener() {
657             if (mOnDismissListener != null) {
658                 mOnDismissListener.onDismiss();
659             }
660         }
661 
startActivity(Intent intent, ResolveInfo resolveInfo)662         private void startActivity(Intent intent, ResolveInfo resolveInfo) {
663             try {
664                 mContext.startActivity(intent);
665             } catch (RuntimeException re) {
666                 CharSequence appLabel = resolveInfo.loadLabel(mContext.getPackageManager());
667                 String message = mContext.getString(
668                         R.string.activitychooserview_choose_application_error, appLabel);
669                 Log.e(LOG_TAG, message);
670                 Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
671             }
672         }
673     }
674 
675     /**
676      * Adapter for backing the list of activities shown in the popup.
677      */
678     private class ActivityChooserViewAdapter extends BaseAdapter {
679 
680         public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE;
681 
682         public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4;
683 
684         private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
685 
686         private static final int ITEM_VIEW_TYPE_FOOTER = 1;
687 
688         private static final int ITEM_VIEW_TYPE_COUNT = 3;
689 
690         private ActivityChooserModel mDataModel;
691 
692         private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
693 
694         private boolean mShowDefaultActivity;
695 
696         private boolean mHighlightDefaultActivity;
697 
698         private boolean mShowFooterView;
699 
setDataModel(ActivityChooserModel dataModel)700         public void setDataModel(ActivityChooserModel dataModel) {
701             ActivityChooserModel oldDataModel = mAdapter.getDataModel();
702             if (oldDataModel != null && isShown()) {
703                 oldDataModel.unregisterObserver(mModelDataSetOberver);
704             }
705             mDataModel = dataModel;
706             if (dataModel != null && isShown()) {
707                 dataModel.registerObserver(mModelDataSetOberver);
708             }
709             notifyDataSetChanged();
710         }
711 
712         @Override
getItemViewType(int position)713         public int getItemViewType(int position) {
714             if (mShowFooterView && position == getCount() - 1) {
715                 return ITEM_VIEW_TYPE_FOOTER;
716             } else {
717                 return ITEM_VIEW_TYPE_ACTIVITY;
718             }
719         }
720 
721         @Override
getViewTypeCount()722         public int getViewTypeCount() {
723             return ITEM_VIEW_TYPE_COUNT;
724         }
725 
getCount()726         public int getCount() {
727             int count = 0;
728             int activityCount = mDataModel.getActivityCount();
729             if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
730                 activityCount--;
731             }
732             count = Math.min(activityCount, mMaxActivityCount);
733             if (mShowFooterView) {
734                 count++;
735             }
736             return count;
737         }
738 
getItem(int position)739         public Object getItem(int position) {
740             final int itemViewType = getItemViewType(position);
741             switch (itemViewType) {
742                 case ITEM_VIEW_TYPE_FOOTER:
743                     return null;
744                 case ITEM_VIEW_TYPE_ACTIVITY:
745                     if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
746                         position++;
747                     }
748                     return mDataModel.getActivity(position);
749                 default:
750                     throw new IllegalArgumentException();
751             }
752         }
753 
getItemId(int position)754         public long getItemId(int position) {
755             return position;
756         }
757 
getView(int position, View convertView, ViewGroup parent)758         public View getView(int position, View convertView, ViewGroup parent) {
759             final int itemViewType = getItemViewType(position);
760             switch (itemViewType) {
761                 case ITEM_VIEW_TYPE_FOOTER:
762                     if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) {
763                         convertView = LayoutInflater.from(getContext()).inflate(
764                                 R.layout.activity_chooser_view_list_item, parent, false);
765                         convertView.setId(ITEM_VIEW_TYPE_FOOTER);
766                         TextView titleView = convertView.findViewById(R.id.title);
767                         titleView.setText(mContext.getString(
768                                 R.string.activity_chooser_view_see_all));
769                     }
770                     return convertView;
771                 case ITEM_VIEW_TYPE_ACTIVITY:
772                     if (convertView == null || convertView.getId() != R.id.list_item) {
773                         convertView = LayoutInflater.from(getContext()).inflate(
774                                 R.layout.activity_chooser_view_list_item, parent, false);
775                     }
776                     PackageManager packageManager = mContext.getPackageManager();
777                     // Set the icon
778                     ImageView iconView = convertView.findViewById(R.id.icon);
779                     ResolveInfo activity = (ResolveInfo) getItem(position);
780                     iconView.setImageDrawable(activity.loadIcon(packageManager));
781                     // Set the title.
782                     TextView titleView = convertView.findViewById(R.id.title);
783                     titleView.setText(activity.loadLabel(packageManager));
784                     // Highlight the default.
785                     if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
786                         convertView.setActivated(true);
787                     } else {
788                         convertView.setActivated(false);
789                     }
790                     return convertView;
791                 default:
792                     throw new IllegalArgumentException();
793             }
794         }
795 
measureContentWidth()796         public int measureContentWidth() {
797             // The user may have specified some of the target not to be shown but we
798             // want to measure all of them since after expansion they should fit.
799             final int oldMaxActivityCount = mMaxActivityCount;
800             mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED;
801 
802             int contentWidth = 0;
803             View itemView = null;
804 
805             final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
806             final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
807             final int count = getCount();
808 
809             for (int i = 0; i < count; i++) {
810                 itemView = getView(i, itemView, null);
811                 itemView.measure(widthMeasureSpec, heightMeasureSpec);
812                 contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth());
813             }
814 
815             mMaxActivityCount = oldMaxActivityCount;
816 
817             return contentWidth;
818         }
819 
setMaxActivityCount(int maxActivityCount)820         public void setMaxActivityCount(int maxActivityCount) {
821             if (mMaxActivityCount != maxActivityCount) {
822                 mMaxActivityCount = maxActivityCount;
823                 notifyDataSetChanged();
824             }
825         }
826 
getDefaultActivity()827         public ResolveInfo getDefaultActivity() {
828             return mDataModel.getDefaultActivity();
829         }
830 
setShowFooterView(boolean showFooterView)831         public void setShowFooterView(boolean showFooterView) {
832             if (mShowFooterView != showFooterView) {
833                 mShowFooterView = showFooterView;
834                 notifyDataSetChanged();
835             }
836         }
837 
getActivityCount()838         public int getActivityCount() {
839             return mDataModel.getActivityCount();
840         }
841 
getHistorySize()842         public int getHistorySize() {
843             return mDataModel.getHistorySize();
844         }
845 
getDataModel()846         public ActivityChooserModel getDataModel() {
847             return mDataModel;
848         }
849 
setShowDefaultActivity(boolean showDefaultActivity, boolean highlightDefaultActivity)850         public void setShowDefaultActivity(boolean showDefaultActivity,
851                 boolean highlightDefaultActivity) {
852             if (mShowDefaultActivity != showDefaultActivity
853                     || mHighlightDefaultActivity != highlightDefaultActivity) {
854                 mShowDefaultActivity = showDefaultActivity;
855                 mHighlightDefaultActivity = highlightDefaultActivity;
856                 notifyDataSetChanged();
857             }
858         }
859 
getShowDefaultActivity()860         public boolean getShowDefaultActivity() {
861             return mShowDefaultActivity;
862         }
863     }
864 }
865