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