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