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