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 defStyle The default style to apply to this view.
208     */
209    public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
210        super(context, attrs, defStyle);
211
212        TypedArray attributesArray = context.obtainStyledAttributes(attrs,
213                R.styleable.ActivityChooserView, defStyle, 0);
214
215        mInitialActivityCount = attributesArray.getInt(
216                R.styleable.ActivityChooserView_initialActivityCount,
217                ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT);
218
219        Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable(
220                R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable);
221
222        attributesArray.recycle();
223
224        LayoutInflater inflater = LayoutInflater.from(mContext);
225        inflater.inflate(R.layout.activity_chooser_view, this, true);
226
227        mCallbacks = new Callbacks();
228
229        mActivityChooserContent = (LinearLayout) findViewById(R.id.activity_chooser_view_content);
230        mActivityChooserContentBackground = mActivityChooserContent.getBackground();
231
232        mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button);
233        mDefaultActivityButton.setOnClickListener(mCallbacks);
234        mDefaultActivityButton.setOnLongClickListener(mCallbacks);
235        mDefaultActivityButtonImage = (ImageView) mDefaultActivityButton.findViewById(R.id.image);
236
237        final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
238        expandButton.setOnClickListener(mCallbacks);
239        expandButton.setAccessibilityDelegate(new AccessibilityDelegate() {
240            @Override
241            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
242                super.onInitializeAccessibilityNodeInfo(host, info);
243                info.setCanOpenPopup(true);
244            }
245        });
246        expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
247            @Override
248            public ListPopupWindow getPopup() {
249                return getListPopupWindow();
250            }
251
252            @Override
253            protected boolean onForwardingStarted() {
254                showPopup();
255                return true;
256            }
257
258            @Override
259            protected boolean onForwardingStopped() {
260                dismissPopup();
261                return true;
262            }
263        });
264        mExpandActivityOverflowButton = expandButton;
265
266        mExpandActivityOverflowButtonImage =
267            (ImageView) expandButton.findViewById(R.id.image);
268        mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable);
269
270        mAdapter = new ActivityChooserViewAdapter();
271        mAdapter.registerDataSetObserver(new DataSetObserver() {
272            @Override
273            public void onChanged() {
274                super.onChanged();
275                updateAppearance();
276            }
277        });
278
279        Resources resources = context.getResources();
280        mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2,
281              resources.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
282    }
283
284    /**
285     * {@inheritDoc}
286     */
287    public void setActivityChooserModel(ActivityChooserModel dataModel) {
288        mAdapter.setDataModel(dataModel);
289        if (isShowingPopup()) {
290            dismissPopup();
291            showPopup();
292        }
293    }
294
295    /**
296     * Sets the background for the button that expands the activity
297     * overflow list.
298     *
299     * <strong>Note:</strong> Clients would like to set this drawable
300     * as a clue about the action the chosen activity will perform. For
301     * example, if a share activity is to be chosen the drawable should
302     * give a clue that sharing is to be performed.
303     *
304     * @param drawable The drawable.
305     */
306    public void setExpandActivityOverflowButtonDrawable(Drawable drawable) {
307        mExpandActivityOverflowButtonImage.setImageDrawable(drawable);
308    }
309
310    /**
311     * Sets the content description for the button that expands the activity
312     * overflow list.
313     *
314     * description as a clue about the action performed by the button.
315     * For example, if a share activity is to be chosen the content
316     * description should be something like "Share with".
317     *
318     * @param resourceId The content description resource id.
319     */
320    public void setExpandActivityOverflowButtonContentDescription(int resourceId) {
321        CharSequence contentDescription = mContext.getString(resourceId);
322        mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
323    }
324
325    /**
326     * Set the provider hosting this view, if applicable.
327     * @hide Internal use only
328     */
329    public void setProvider(ActionProvider provider) {
330        mProvider = provider;
331    }
332
333    /**
334     * Shows the popup window with activities.
335     *
336     * @return True if the popup was shown, false if already showing.
337     */
338    public boolean showPopup() {
339        if (isShowingPopup() || !mIsAttachedToWindow) {
340            return false;
341        }
342        mIsSelectingDefaultActivity = false;
343        showPopupUnchecked(mInitialActivityCount);
344        return true;
345    }
346
347    /**
348     * Shows the popup no matter if it was already showing.
349     *
350     * @param maxActivityCount The max number of activities to display.
351     */
352    private void showPopupUnchecked(int maxActivityCount) {
353        if (mAdapter.getDataModel() == null) {
354            throw new IllegalStateException("No data model. Did you call #setDataModel?");
355        }
356
357        getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
358
359        final boolean defaultActivityButtonShown =
360            mDefaultActivityButton.getVisibility() == VISIBLE;
361
362        final int activityCount = mAdapter.getActivityCount();
363        final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0;
364        if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED
365                && activityCount > maxActivityCount + maxActivityCountOffset) {
366            mAdapter.setShowFooterView(true);
367            mAdapter.setMaxActivityCount(maxActivityCount - 1);
368        } else {
369            mAdapter.setShowFooterView(false);
370            mAdapter.setMaxActivityCount(maxActivityCount);
371        }
372
373        ListPopupWindow popupWindow = getListPopupWindow();
374        if (!popupWindow.isShowing()) {
375            if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) {
376                mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown);
377            } else {
378                mAdapter.setShowDefaultActivity(false, false);
379            }
380            final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth);
381            popupWindow.setContentWidth(contentWidth);
382            popupWindow.show();
383            if (mProvider != null) {
384                mProvider.subUiVisibilityChanged(true);
385            }
386            popupWindow.getListView().setContentDescription(mContext.getString(
387                    R.string.activitychooserview_choose_application));
388        }
389    }
390
391    /**
392     * Dismisses the popup window with activities.
393     *
394     * @return True if dismissed, false if already dismissed.
395     */
396    public boolean dismissPopup() {
397        if (isShowingPopup()) {
398            getListPopupWindow().dismiss();
399            ViewTreeObserver viewTreeObserver = getViewTreeObserver();
400            if (viewTreeObserver.isAlive()) {
401                viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
402            }
403        }
404        return true;
405    }
406
407    /**
408     * Gets whether the popup window with activities is shown.
409     *
410     * @return True if the popup is shown.
411     */
412    public boolean isShowingPopup() {
413        return getListPopupWindow().isShowing();
414    }
415
416    @Override
417    protected void onAttachedToWindow() {
418        super.onAttachedToWindow();
419        ActivityChooserModel dataModel = mAdapter.getDataModel();
420        if (dataModel != null) {
421            dataModel.registerObserver(mModelDataSetOberver);
422        }
423        mIsAttachedToWindow = true;
424    }
425
426    @Override
427    protected void onDetachedFromWindow() {
428        super.onDetachedFromWindow();
429        ActivityChooserModel dataModel = mAdapter.getDataModel();
430        if (dataModel != null) {
431            dataModel.unregisterObserver(mModelDataSetOberver);
432        }
433        ViewTreeObserver viewTreeObserver = getViewTreeObserver();
434        if (viewTreeObserver.isAlive()) {
435            viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
436        }
437        if (isShowingPopup()) {
438            dismissPopup();
439        }
440        mIsAttachedToWindow = false;
441    }
442
443    @Override
444    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
445        View child = mActivityChooserContent;
446        // If the default action is not visible we want to be as tall as the
447        // ActionBar so if this widget is used in the latter it will look as
448        // a normal action button.
449        if (mDefaultActivityButton.getVisibility() != VISIBLE) {
450            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
451                    MeasureSpec.EXACTLY);
452        }
453        measureChild(child, widthMeasureSpec, heightMeasureSpec);
454        setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
455    }
456
457    @Override
458    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
459        mActivityChooserContent.layout(0, 0, right - left, bottom - top);
460        if (!isShowingPopup()) {
461            dismissPopup();
462        }
463    }
464
465    public ActivityChooserModel getDataModel() {
466        return mAdapter.getDataModel();
467    }
468
469    /**
470     * Sets a listener to receive a callback when the popup is dismissed.
471     *
472     * @param listener The listener to be notified.
473     */
474    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
475        mOnDismissListener = listener;
476    }
477
478    /**
479     * Sets the initial count of items shown in the activities popup
480     * i.e. the items before the popup is expanded. This is an upper
481     * bound since it is not guaranteed that such number of intent
482     * handlers exist.
483     *
484     * @param itemCount The initial popup item count.
485     */
486    public void setInitialActivityCount(int itemCount) {
487        mInitialActivityCount = itemCount;
488    }
489
490    /**
491     * Sets a content description of the default action button. This
492     * resource should be a string taking one formatting argument and
493     * will be used for formatting the content description of the button
494     * dynamically as the default target changes. For example, a resource
495     * pointing to the string "share with %1$s" will result in a content
496     * description "share with Bluetooth" for the Bluetooth activity.
497     *
498     * @param resourceId The resource id.
499     */
500    public void setDefaultActionButtonContentDescription(int resourceId) {
501        mDefaultActionButtonContentDescription = resourceId;
502    }
503
504    /**
505     * Gets the list popup window which is lazily initialized.
506     *
507     * @return The popup.
508     */
509    private ListPopupWindow getListPopupWindow() {
510        if (mListPopupWindow == null) {
511            mListPopupWindow = new ListPopupWindow(getContext());
512            mListPopupWindow.setAdapter(mAdapter);
513            mListPopupWindow.setAnchorView(ActivityChooserView.this);
514            mListPopupWindow.setModal(true);
515            mListPopupWindow.setOnItemClickListener(mCallbacks);
516            mListPopupWindow.setOnDismissListener(mCallbacks);
517        }
518        return mListPopupWindow;
519    }
520
521    /**
522     * Updates the buttons state.
523     */
524    private void updateAppearance() {
525        // Expand overflow button.
526        if (mAdapter.getCount() > 0) {
527            mExpandActivityOverflowButton.setEnabled(true);
528        } else {
529            mExpandActivityOverflowButton.setEnabled(false);
530        }
531        // Default activity button.
532        final int activityCount = mAdapter.getActivityCount();
533        final int historySize = mAdapter.getHistorySize();
534        if (activityCount==1 || activityCount > 1 && historySize > 0) {
535            mDefaultActivityButton.setVisibility(VISIBLE);
536            ResolveInfo activity = mAdapter.getDefaultActivity();
537            PackageManager packageManager = mContext.getPackageManager();
538            mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
539            if (mDefaultActionButtonContentDescription != 0) {
540                CharSequence label = activity.loadLabel(packageManager);
541                String contentDescription = mContext.getString(
542                        mDefaultActionButtonContentDescription, label);
543                mDefaultActivityButton.setContentDescription(contentDescription);
544            }
545        } else {
546            mDefaultActivityButton.setVisibility(View.GONE);
547        }
548        // Activity chooser content.
549        if (mDefaultActivityButton.getVisibility() == VISIBLE) {
550            mActivityChooserContent.setBackground(mActivityChooserContentBackground);
551        } else {
552            mActivityChooserContent.setBackground(null);
553        }
554    }
555
556    /**
557     * Interface implementation to avoid publishing them in the APIs.
558     */
559    private class Callbacks implements AdapterView.OnItemClickListener,
560            View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener {
561
562        // AdapterView#OnItemClickListener
563        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
564            ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter();
565            final int itemViewType = adapter.getItemViewType(position);
566            switch (itemViewType) {
567                case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: {
568                    showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED);
569                } break;
570                case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: {
571                    dismissPopup();
572                    if (mIsSelectingDefaultActivity) {
573                        // The item at position zero is the default already.
574                        if (position > 0) {
575                            mAdapter.getDataModel().setDefaultActivity(position);
576                        }
577                    } else {
578                        // If the default target is not shown in the list, the first
579                        // item in the model is default action => adjust index
580                        position = mAdapter.getShowDefaultActivity() ? position : position + 1;
581                        Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
582                        if (launchIntent != null) {
583                            launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
584                            ResolveInfo resolveInfo = mAdapter.getDataModel().getActivity(position);
585                            startActivity(launchIntent, resolveInfo);
586                        }
587                    }
588                } break;
589                default:
590                    throw new IllegalArgumentException();
591            }
592        }
593
594        // View.OnClickListener
595        public void onClick(View view) {
596            if (view == mDefaultActivityButton) {
597                dismissPopup();
598                ResolveInfo defaultActivity = mAdapter.getDefaultActivity();
599                final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
600                Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
601                if (launchIntent != null) {
602                    launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
603                    startActivity(launchIntent, defaultActivity);
604                }
605            } else if (view == mExpandActivityOverflowButton) {
606                mIsSelectingDefaultActivity = false;
607                showPopupUnchecked(mInitialActivityCount);
608            } else {
609                throw new IllegalArgumentException();
610            }
611        }
612
613        // OnLongClickListener#onLongClick
614        @Override
615        public boolean onLongClick(View view) {
616            if (view == mDefaultActivityButton) {
617                if (mAdapter.getCount() > 0) {
618                    mIsSelectingDefaultActivity = true;
619                    showPopupUnchecked(mInitialActivityCount);
620                }
621            } else {
622                throw new IllegalArgumentException();
623            }
624            return true;
625        }
626
627        // PopUpWindow.OnDismissListener#onDismiss
628        public void onDismiss() {
629            notifyOnDismissListener();
630            if (mProvider != null) {
631                mProvider.subUiVisibilityChanged(false);
632            }
633        }
634
635        private void notifyOnDismissListener() {
636            if (mOnDismissListener != null) {
637                mOnDismissListener.onDismiss();
638            }
639        }
640
641        private void startActivity(Intent intent, ResolveInfo resolveInfo) {
642            try {
643                mContext.startActivity(intent);
644            } catch (RuntimeException re) {
645                CharSequence appLabel = resolveInfo.loadLabel(mContext.getPackageManager());
646                String message = mContext.getString(
647                        R.string.activitychooserview_choose_application_error, appLabel);
648                Log.e(LOG_TAG, message);
649                Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
650            }
651        }
652    }
653
654    /**
655     * Adapter for backing the list of activities shown in the popup.
656     */
657    private class ActivityChooserViewAdapter extends BaseAdapter {
658
659        public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE;
660
661        public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4;
662
663        private static final int ITEM_VIEW_TYPE_ACTIVITY = 0;
664
665        private static final int ITEM_VIEW_TYPE_FOOTER = 1;
666
667        private static final int ITEM_VIEW_TYPE_COUNT = 3;
668
669        private ActivityChooserModel mDataModel;
670
671        private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT;
672
673        private boolean mShowDefaultActivity;
674
675        private boolean mHighlightDefaultActivity;
676
677        private boolean mShowFooterView;
678
679        public void setDataModel(ActivityChooserModel dataModel) {
680            ActivityChooserModel oldDataModel = mAdapter.getDataModel();
681            if (oldDataModel != null && isShown()) {
682                oldDataModel.unregisterObserver(mModelDataSetOberver);
683            }
684            mDataModel = dataModel;
685            if (dataModel != null && isShown()) {
686                dataModel.registerObserver(mModelDataSetOberver);
687            }
688            notifyDataSetChanged();
689        }
690
691        @Override
692        public int getItemViewType(int position) {
693            if (mShowFooterView && position == getCount() - 1) {
694                return ITEM_VIEW_TYPE_FOOTER;
695            } else {
696                return ITEM_VIEW_TYPE_ACTIVITY;
697            }
698        }
699
700        @Override
701        public int getViewTypeCount() {
702            return ITEM_VIEW_TYPE_COUNT;
703        }
704
705        public int getCount() {
706            int count = 0;
707            int activityCount = mDataModel.getActivityCount();
708            if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
709                activityCount--;
710            }
711            count = Math.min(activityCount, mMaxActivityCount);
712            if (mShowFooterView) {
713                count++;
714            }
715            return count;
716        }
717
718        public Object getItem(int position) {
719            final int itemViewType = getItemViewType(position);
720            switch (itemViewType) {
721                case ITEM_VIEW_TYPE_FOOTER:
722                    return null;
723                case ITEM_VIEW_TYPE_ACTIVITY:
724                    if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) {
725                        position++;
726                    }
727                    return mDataModel.getActivity(position);
728                default:
729                    throw new IllegalArgumentException();
730            }
731        }
732
733        public long getItemId(int position) {
734            return position;
735        }
736
737        public View getView(int position, View convertView, ViewGroup parent) {
738            final int itemViewType = getItemViewType(position);
739            switch (itemViewType) {
740                case ITEM_VIEW_TYPE_FOOTER:
741                    if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) {
742                        convertView = LayoutInflater.from(getContext()).inflate(
743                                R.layout.activity_chooser_view_list_item, parent, false);
744                        convertView.setId(ITEM_VIEW_TYPE_FOOTER);
745                        TextView titleView = (TextView) convertView.findViewById(R.id.title);
746                        titleView.setText(mContext.getString(
747                                R.string.activity_chooser_view_see_all));
748                    }
749                    return convertView;
750                case ITEM_VIEW_TYPE_ACTIVITY:
751                    if (convertView == null || convertView.getId() != R.id.list_item) {
752                        convertView = LayoutInflater.from(getContext()).inflate(
753                                R.layout.activity_chooser_view_list_item, parent, false);
754                    }
755                    PackageManager packageManager = mContext.getPackageManager();
756                    // Set the icon
757                    ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
758                    ResolveInfo activity = (ResolveInfo) getItem(position);
759                    iconView.setImageDrawable(activity.loadIcon(packageManager));
760                    // Set the title.
761                    TextView titleView = (TextView) convertView.findViewById(R.id.title);
762                    titleView.setText(activity.loadLabel(packageManager));
763                    // Highlight the default.
764                    if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) {
765                        convertView.setActivated(true);
766                    } else {
767                        convertView.setActivated(false);
768                    }
769                    return convertView;
770                default:
771                    throw new IllegalArgumentException();
772            }
773        }
774
775        public int measureContentWidth() {
776            // The user may have specified some of the target not to be shown but we
777            // want to measure all of them since after expansion they should fit.
778            final int oldMaxActivityCount = mMaxActivityCount;
779            mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED;
780
781            int contentWidth = 0;
782            View itemView = null;
783
784            final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
785            final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
786            final int count = getCount();
787
788            for (int i = 0; i < count; i++) {
789                itemView = getView(i, itemView, null);
790                itemView.measure(widthMeasureSpec, heightMeasureSpec);
791                contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth());
792            }
793
794            mMaxActivityCount = oldMaxActivityCount;
795
796            return contentWidth;
797        }
798
799        public void setMaxActivityCount(int maxActivityCount) {
800            if (mMaxActivityCount != maxActivityCount) {
801                mMaxActivityCount = maxActivityCount;
802                notifyDataSetChanged();
803            }
804        }
805
806        public ResolveInfo getDefaultActivity() {
807            return mDataModel.getDefaultActivity();
808        }
809
810        public void setShowFooterView(boolean showFooterView) {
811            if (mShowFooterView != showFooterView) {
812                mShowFooterView = showFooterView;
813                notifyDataSetChanged();
814            }
815        }
816
817        public int getActivityCount() {
818            return mDataModel.getActivityCount();
819        }
820
821        public int getHistorySize() {
822            return mDataModel.getHistorySize();
823        }
824
825        public ActivityChooserModel getDataModel() {
826            return mDataModel;
827        }
828
829        public void setShowDefaultActivity(boolean showDefaultActivity,
830                boolean highlightDefaultActivity) {
831            if (mShowDefaultActivity != showDefaultActivity
832                    || mHighlightDefaultActivity != highlightDefaultActivity) {
833                mShowDefaultActivity = showDefaultActivity;
834                mHighlightDefaultActivity = highlightDefaultActivity;
835                notifyDataSetChanged();
836            }
837        }
838
839        public boolean getShowDefaultActivity() {
840            return mShowDefaultActivity;
841        }
842    }
843}
844