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