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