ActivityChooserView.java revision 177f37367e271964d5faf70cf19384f23ea8df0b
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 android.annotation.StringRes;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.pm.ResolveInfo;
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.database.DataSetObserver;
27import android.graphics.drawable.Drawable;
28import android.util.AttributeSet;
29import android.util.Log;
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.view.accessibility.AccessibilityNodeInfo;
37import android.widget.ActivityChooserModel.ActivityChooserModelClient;
38
39import com.android.internal.R;
40import com.android.internal.view.menu.ShowableListMenu;
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 */
65public 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     */
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     */
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     */
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     */
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 ShowableListMenu 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
430    public boolean isShowingPopup() {
431        return getListPopupWindow().isShowing();
432    }
433
434    @Override
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
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
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
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
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     */
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     */
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     */
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     */
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     */
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
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
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
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
646        public void onDismiss() {
647            notifyOnDismissListener();
648            if (mProvider != null) {
649                mProvider.subUiVisibilityChanged(false);
650            }
651        }
652
653        private void notifyOnDismissListener() {
654            if (mOnDismissListener != null) {
655                mOnDismissListener.onDismiss();
656            }
657        }
658
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
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
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
719        public int getViewTypeCount() {
720            return ITEM_VIEW_TYPE_COUNT;
721        }
722
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
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
751        public long getItemId(int position) {
752            return position;
753        }
754
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
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
817        public void setMaxActivityCount(int maxActivityCount) {
818            if (mMaxActivityCount != maxActivityCount) {
819                mMaxActivityCount = maxActivityCount;
820                notifyDataSetChanged();
821            }
822        }
823
824        public ResolveInfo getDefaultActivity() {
825            return mDataModel.getDefaultActivity();
826        }
827
828        public void setShowFooterView(boolean showFooterView) {
829            if (mShowFooterView != showFooterView) {
830                mShowFooterView = showFooterView;
831                notifyDataSetChanged();
832            }
833        }
834
835        public int getActivityCount() {
836            return mDataModel.getActivityCount();
837        }
838
839        public int getHistorySize() {
840            return mDataModel.getHistorySize();
841        }
842
843        public ActivityChooserModel getDataModel() {
844            return mDataModel;
845        }
846
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
857        public boolean getShowDefaultActivity() {
858            return mShowDefaultActivity;
859        }
860    }
861}
862