Home | History | Annotate | Download | only in widget
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    429     public boolean isShowingPopup() {
    430         return getListPopupWindow().isShowing();
    431     }
    432 
    433     @Override
    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
    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
    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
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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
    632         public boolean onLongClick(View view) {
    633             if (view == mDefaultActivityButton) {
    634                 if (mAdapter.getCount() > 0) {
    635                     mIsSelectingDefaultActivity = true;
    636                     showPopupUnchecked(mInitialActivityCount);
    637                 }
    638             } else {
    639                 throw new IllegalArgumentException();
    640             }
    641             return true;
    642         }
    643 
    644         // PopUpWindow.OnDismissListener#onDismiss
    645         public void onDismiss() {
    646             notifyOnDismissListener();
    647             if (mProvider != null) {
    648                 mProvider.subUiVisibilityChanged(false);
    649             }
    650         }
    651 
    652         private void notifyOnDismissListener() {
    653             if (mOnDismissListener != null) {
    654                 mOnDismissListener.onDismiss();
    655             }
    656         }
    657 
    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 
    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
    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
    718         public int getViewTypeCount() {
    719             return ITEM_VIEW_TYPE_COUNT;
    720         }
    721 
    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 
    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 
    750         public long getItemId(int position) {
    751             return position;
    752         }
    753 
    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 
    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 
    816         public void setMaxActivityCount(int maxActivityCount) {
    817             if (mMaxActivityCount != maxActivityCount) {
    818                 mMaxActivityCount = maxActivityCount;
    819                 notifyDataSetChanged();
    820             }
    821         }
    822 
    823         public ResolveInfo getDefaultActivity() {
    824             return mDataModel.getDefaultActivity();
    825         }
    826 
    827         public void setShowFooterView(boolean showFooterView) {
    828             if (mShowFooterView != showFooterView) {
    829                 mShowFooterView = showFooterView;
    830                 notifyDataSetChanged();
    831             }
    832         }
    833 
    834         public int getActivityCount() {
    835             return mDataModel.getActivityCount();
    836         }
    837 
    838         public int getHistorySize() {
    839             return mDataModel.getHistorySize();
    840         }
    841 
    842         public ActivityChooserModel getDataModel() {
    843             return mDataModel;
    844         }
    845 
    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 
    856         public boolean getShowDefaultActivity() {
    857             return mShowDefaultActivity;
    858         }
    859     }
    860 }
    861