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