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