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