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