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