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