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