|  | /* | 
|  | * Copyright (C) 2011 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.widget; | 
|  |  | 
|  | import android.annotation.StringRes; | 
|  | import android.compat.annotation.UnsupportedAppUsage; | 
|  | import android.content.Context; | 
|  | import android.content.Intent; | 
|  | import android.content.pm.PackageManager; | 
|  | import android.content.pm.ResolveInfo; | 
|  | import android.content.res.Resources; | 
|  | import android.content.res.TypedArray; | 
|  | import android.database.DataSetObserver; | 
|  | import android.graphics.Color; | 
|  | import android.graphics.drawable.ColorDrawable; | 
|  | import android.graphics.drawable.Drawable; | 
|  | import android.util.AttributeSet; | 
|  | import android.util.Log; | 
|  | import android.view.ActionProvider; | 
|  | import android.view.LayoutInflater; | 
|  | import android.view.View; | 
|  | import android.view.ViewGroup; | 
|  | import android.view.ViewTreeObserver; | 
|  | import android.view.ViewTreeObserver.OnGlobalLayoutListener; | 
|  | import android.view.accessibility.AccessibilityNodeInfo; | 
|  | import android.widget.ActivityChooserModel.ActivityChooserModelClient; | 
|  |  | 
|  | import com.android.internal.R; | 
|  | import com.android.internal.view.menu.ShowableListMenu; | 
|  |  | 
|  | /** | 
|  | * This class is a view for choosing an activity for handling a given {@link Intent}. | 
|  | * <p> | 
|  | * The view is composed of two adjacent buttons: | 
|  | * <ul> | 
|  | * <li> | 
|  | * The left button is an immediate action and allows one click activity choosing. | 
|  | * Tapping this button immediately executes the intent without requiring any further | 
|  | * user input. Long press on this button shows a popup for changing the default | 
|  | * activity. | 
|  | * </li> | 
|  | * <li> | 
|  | * The right button is an overflow action and provides an optimized menu | 
|  | * of additional activities. Tapping this button shows a popup anchored to this | 
|  | * view, listing the most frequently used activities. This list is initially | 
|  | * limited to a small number of items in frequency used order. The last item, | 
|  | * "Show all..." serves as an affordance to display all available activities. | 
|  | * </li> | 
|  | * </ul> | 
|  | * </p> | 
|  | * | 
|  | * @hide | 
|  | */ | 
|  | public class ActivityChooserView extends ViewGroup implements ActivityChooserModelClient { | 
|  |  | 
|  | private static final String LOG_TAG = "ActivityChooserView"; | 
|  |  | 
|  | /** | 
|  | * An adapter for displaying the activities in an {@link AdapterView}. | 
|  | */ | 
|  | private final ActivityChooserViewAdapter mAdapter; | 
|  |  | 
|  | /** | 
|  | * Implementation of various interfaces to avoid publishing them in the APIs. | 
|  | */ | 
|  | private final Callbacks mCallbacks; | 
|  |  | 
|  | /** | 
|  | * The content of this view. | 
|  | */ | 
|  | private final LinearLayout mActivityChooserContent; | 
|  |  | 
|  | /** | 
|  | * Stores the background drawable to allow hiding and latter showing. | 
|  | */ | 
|  | private final Drawable mActivityChooserContentBackground; | 
|  |  | 
|  | /** | 
|  | * The expand activities action button; | 
|  | */ | 
|  | private final FrameLayout mExpandActivityOverflowButton; | 
|  |  | 
|  | /** | 
|  | * The image for the expand activities action button; | 
|  | */ | 
|  | private final ImageView mExpandActivityOverflowButtonImage; | 
|  |  | 
|  | /** | 
|  | * The default activities action button; | 
|  | */ | 
|  | private final FrameLayout mDefaultActivityButton; | 
|  |  | 
|  | /** | 
|  | * The image for the default activities action button; | 
|  | */ | 
|  | private final ImageView mDefaultActivityButtonImage; | 
|  |  | 
|  | /** | 
|  | * The maximal width of the list popup. | 
|  | */ | 
|  | private final int mListPopupMaxWidth; | 
|  |  | 
|  | /** | 
|  | * The ActionProvider hosting this view, if applicable. | 
|  | */ | 
|  | ActionProvider mProvider; | 
|  |  | 
|  | /** | 
|  | * Observer for the model data. | 
|  | */ | 
|  | private final DataSetObserver mModelDataSetOberver = new DataSetObserver() { | 
|  |  | 
|  | @Override | 
|  | public void onChanged() { | 
|  | super.onChanged(); | 
|  | mAdapter.notifyDataSetChanged(); | 
|  | } | 
|  | @Override | 
|  | public void onInvalidated() { | 
|  | super.onInvalidated(); | 
|  | mAdapter.notifyDataSetInvalidated(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | private final OnGlobalLayoutListener mOnGlobalLayoutListener = new OnGlobalLayoutListener() { | 
|  | @Override | 
|  | public void onGlobalLayout() { | 
|  | if (isShowingPopup()) { | 
|  | if (!isShown()) { | 
|  | getListPopupWindow().dismiss(); | 
|  | } else { | 
|  | getListPopupWindow().show(); | 
|  | if (mProvider != null) { | 
|  | mProvider.subUiVisibilityChanged(true); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Popup window for showing the activity overflow list. | 
|  | */ | 
|  | private ListPopupWindow mListPopupWindow; | 
|  |  | 
|  | /** | 
|  | * Listener for the dismissal of the popup/alert. | 
|  | */ | 
|  | private PopupWindow.OnDismissListener mOnDismissListener; | 
|  |  | 
|  | /** | 
|  | * Flag whether a default activity currently being selected. | 
|  | */ | 
|  | private boolean mIsSelectingDefaultActivity; | 
|  |  | 
|  | /** | 
|  | * The count of activities in the popup. | 
|  | */ | 
|  | private int mInitialActivityCount = ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT; | 
|  |  | 
|  | /** | 
|  | * Flag whether this view is attached to a window. | 
|  | */ | 
|  | private boolean mIsAttachedToWindow; | 
|  |  | 
|  | /** | 
|  | * String resource for formatting content description of the default target. | 
|  | */ | 
|  | private int mDefaultActionButtonContentDescription; | 
|  |  | 
|  | /** | 
|  | * Create a new instance. | 
|  | * | 
|  | * @param context The application environment. | 
|  | */ | 
|  | public ActivityChooserView(Context context) { | 
|  | this(context, null); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new instance. | 
|  | * | 
|  | * @param context The application environment. | 
|  | * @param attrs A collection of attributes. | 
|  | */ | 
|  | public ActivityChooserView(Context context, AttributeSet attrs) { | 
|  | this(context, attrs, 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new instance. | 
|  | * | 
|  | * @param context The application environment. | 
|  | * @param attrs A collection of attributes. | 
|  | * @param defStyleAttr An attribute in the current theme that contains a | 
|  | *        reference to a style resource that supplies default values for | 
|  | *        the view. Can be 0 to not look for defaults. | 
|  | */ | 
|  | public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) { | 
|  | this(context, attrs, defStyleAttr, 0); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new instance. | 
|  | * | 
|  | * @param context The application environment. | 
|  | * @param attrs A collection of attributes. | 
|  | * @param defStyleAttr An attribute in the current theme that contains a | 
|  | *        reference to a style resource that supplies default values for | 
|  | *        the view. Can be 0 to not look for defaults. | 
|  | * @param defStyleRes A resource identifier of a style resource that | 
|  | *        supplies default values for the view, used only if | 
|  | *        defStyleAttr is 0 or can not be found in the theme. Can be 0 | 
|  | *        to not look for defaults. | 
|  | */ | 
|  | public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | 
|  | super(context, attrs, defStyleAttr, defStyleRes); | 
|  |  | 
|  | TypedArray attributesArray = context.obtainStyledAttributes(attrs, | 
|  | R.styleable.ActivityChooserView, defStyleAttr, defStyleRes); | 
|  | saveAttributeDataForStyleable(context, R.styleable.ActivityChooserView, attrs, | 
|  | attributesArray, defStyleAttr, defStyleRes); | 
|  |  | 
|  | mInitialActivityCount = attributesArray.getInt( | 
|  | R.styleable.ActivityChooserView_initialActivityCount, | 
|  | ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_DEFAULT); | 
|  |  | 
|  | Drawable expandActivityOverflowButtonDrawable = attributesArray.getDrawable( | 
|  | R.styleable.ActivityChooserView_expandActivityOverflowButtonDrawable); | 
|  |  | 
|  | attributesArray.recycle(); | 
|  |  | 
|  | LayoutInflater inflater = LayoutInflater.from(mContext); | 
|  | inflater.inflate(R.layout.activity_chooser_view, this, true); | 
|  |  | 
|  | mCallbacks = new Callbacks(); | 
|  |  | 
|  | mActivityChooserContent = (LinearLayout) findViewById(R.id.activity_chooser_view_content); | 
|  | mActivityChooserContentBackground = mActivityChooserContent.getBackground(); | 
|  |  | 
|  | mDefaultActivityButton = (FrameLayout) findViewById(R.id.default_activity_button); | 
|  | mDefaultActivityButton.setOnClickListener(mCallbacks); | 
|  | mDefaultActivityButton.setOnLongClickListener(mCallbacks); | 
|  | mDefaultActivityButtonImage = mDefaultActivityButton.findViewById(R.id.image); | 
|  |  | 
|  | final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button); | 
|  | expandButton.setOnClickListener(mCallbacks); | 
|  | expandButton.setAccessibilityDelegate(new AccessibilityDelegate() { | 
|  | @Override | 
|  | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { | 
|  | super.onInitializeAccessibilityNodeInfo(host, info); | 
|  | info.setCanOpenPopup(true); | 
|  | } | 
|  | }); | 
|  | expandButton.setOnTouchListener(new ForwardingListener(expandButton) { | 
|  | @Override | 
|  | public ShowableListMenu getPopup() { | 
|  | return getListPopupWindow(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean onForwardingStarted() { | 
|  | showPopup(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected boolean onForwardingStopped() { | 
|  | dismissPopup(); | 
|  | return true; | 
|  | } | 
|  | }); | 
|  | mExpandActivityOverflowButton = expandButton; | 
|  |  | 
|  | mExpandActivityOverflowButtonImage = | 
|  | expandButton.findViewById(R.id.image); | 
|  | mExpandActivityOverflowButtonImage.setImageDrawable(expandActivityOverflowButtonDrawable); | 
|  |  | 
|  | mAdapter = new ActivityChooserViewAdapter(); | 
|  | mAdapter.registerDataSetObserver(new DataSetObserver() { | 
|  | @Override | 
|  | public void onChanged() { | 
|  | super.onChanged(); | 
|  | updateAppearance(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | Resources resources = context.getResources(); | 
|  | mListPopupMaxWidth = Math.max(resources.getDisplayMetrics().widthPixels / 2, | 
|  | resources.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * {@inheritDoc} | 
|  | */ | 
|  | public void setActivityChooserModel(ActivityChooserModel dataModel) { | 
|  | mAdapter.setDataModel(dataModel); | 
|  | if (isShowingPopup()) { | 
|  | dismissPopup(); | 
|  | showPopup(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the background for the button that expands the activity | 
|  | * overflow list. | 
|  | * | 
|  | * <strong>Note:</strong> Clients would like to set this drawable | 
|  | * as a clue about the action the chosen activity will perform. For | 
|  | * example, if a share activity is to be chosen the drawable should | 
|  | * give a clue that sharing is to be performed. | 
|  | * | 
|  | * @param drawable The drawable. | 
|  | */ | 
|  | @UnsupportedAppUsage | 
|  | public void setExpandActivityOverflowButtonDrawable(Drawable drawable) { | 
|  | mExpandActivityOverflowButtonImage.setImageDrawable(drawable); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the content description for the button that expands the activity | 
|  | * overflow list. | 
|  | * | 
|  | * description as a clue about the action performed by the button. | 
|  | * For example, if a share activity is to be chosen the content | 
|  | * description should be something like "Share with". | 
|  | * | 
|  | * @param resourceId The content description resource id. | 
|  | */ | 
|  | public void setExpandActivityOverflowButtonContentDescription(@StringRes int resourceId) { | 
|  | CharSequence contentDescription = mContext.getString(resourceId); | 
|  | mExpandActivityOverflowButtonImage.setContentDescription(contentDescription); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set the provider hosting this view, if applicable. | 
|  | * @hide Internal use only | 
|  | */ | 
|  | public void setProvider(ActionProvider provider) { | 
|  | mProvider = provider; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Shows the popup window with activities. | 
|  | * | 
|  | * @return True if the popup was shown, false if already showing. | 
|  | */ | 
|  | public boolean showPopup() { | 
|  | if (isShowingPopup() || !mIsAttachedToWindow) { | 
|  | return false; | 
|  | } | 
|  | mIsSelectingDefaultActivity = false; | 
|  | showPopupUnchecked(mInitialActivityCount); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Shows the popup no matter if it was already showing. | 
|  | * | 
|  | * @param maxActivityCount The max number of activities to display. | 
|  | */ | 
|  | private void showPopupUnchecked(int maxActivityCount) { | 
|  | if (mAdapter.getDataModel() == null) { | 
|  | throw new IllegalStateException("No data model. Did you call #setDataModel?"); | 
|  | } | 
|  |  | 
|  | getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener); | 
|  |  | 
|  | final boolean defaultActivityButtonShown = | 
|  | mDefaultActivityButton.getVisibility() == VISIBLE; | 
|  |  | 
|  | final int activityCount = mAdapter.getActivityCount(); | 
|  | final int maxActivityCountOffset = defaultActivityButtonShown ? 1 : 0; | 
|  | if (maxActivityCount != ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED | 
|  | && activityCount > maxActivityCount + maxActivityCountOffset) { | 
|  | mAdapter.setShowFooterView(true); | 
|  | mAdapter.setMaxActivityCount(maxActivityCount - 1); | 
|  | } else { | 
|  | mAdapter.setShowFooterView(false); | 
|  | mAdapter.setMaxActivityCount(maxActivityCount); | 
|  | } | 
|  |  | 
|  | ListPopupWindow popupWindow = getListPopupWindow(); | 
|  | if (!popupWindow.isShowing()) { | 
|  | if (mIsSelectingDefaultActivity || !defaultActivityButtonShown) { | 
|  | mAdapter.setShowDefaultActivity(true, defaultActivityButtonShown); | 
|  | } else { | 
|  | mAdapter.setShowDefaultActivity(false, false); | 
|  | } | 
|  | final int contentWidth = Math.min(mAdapter.measureContentWidth(), mListPopupMaxWidth); | 
|  | popupWindow.setContentWidth(contentWidth); | 
|  | popupWindow.show(); | 
|  | if (mProvider != null) { | 
|  | mProvider.subUiVisibilityChanged(true); | 
|  | } | 
|  | popupWindow.getListView().setContentDescription(mContext.getString( | 
|  | R.string.activitychooserview_choose_application)); | 
|  | popupWindow.getListView().setSelector(new ColorDrawable(Color.TRANSPARENT)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Dismisses the popup window with activities. | 
|  | * | 
|  | * @return True if dismissed, false if already dismissed. | 
|  | */ | 
|  | public boolean dismissPopup() { | 
|  | if (isShowingPopup()) { | 
|  | getListPopupWindow().dismiss(); | 
|  | ViewTreeObserver viewTreeObserver = getViewTreeObserver(); | 
|  | if (viewTreeObserver.isAlive()) { | 
|  | viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets whether the popup window with activities is shown. | 
|  | * | 
|  | * @return True if the popup is shown. | 
|  | */ | 
|  | public boolean isShowingPopup() { | 
|  | return getListPopupWindow().isShowing(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onAttachedToWindow() { | 
|  | super.onAttachedToWindow(); | 
|  | ActivityChooserModel dataModel = mAdapter.getDataModel(); | 
|  | if (dataModel != null) { | 
|  | dataModel.registerObserver(mModelDataSetOberver); | 
|  | } | 
|  | mIsAttachedToWindow = true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onDetachedFromWindow() { | 
|  | super.onDetachedFromWindow(); | 
|  | ActivityChooserModel dataModel = mAdapter.getDataModel(); | 
|  | if (dataModel != null) { | 
|  | dataModel.unregisterObserver(mModelDataSetOberver); | 
|  | } | 
|  | ViewTreeObserver viewTreeObserver = getViewTreeObserver(); | 
|  | if (viewTreeObserver.isAlive()) { | 
|  | viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener); | 
|  | } | 
|  | if (isShowingPopup()) { | 
|  | dismissPopup(); | 
|  | } | 
|  | mIsAttachedToWindow = false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | 
|  | View child = mActivityChooserContent; | 
|  | // If the default action is not visible we want to be as tall as the | 
|  | // ActionBar so if this widget is used in the latter it will look as | 
|  | // a normal action button. | 
|  | if (mDefaultActivityButton.getVisibility() != VISIBLE) { | 
|  | heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), | 
|  | MeasureSpec.EXACTLY); | 
|  | } | 
|  | measureChild(child, widthMeasureSpec, heightMeasureSpec); | 
|  | setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | 
|  | mActivityChooserContent.layout(0, 0, right - left, bottom - top); | 
|  | if (!isShowingPopup()) { | 
|  | dismissPopup(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public ActivityChooserModel getDataModel() { | 
|  | return mAdapter.getDataModel(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets a listener to receive a callback when the popup is dismissed. | 
|  | * | 
|  | * @param listener The listener to be notified. | 
|  | */ | 
|  | public void setOnDismissListener(PopupWindow.OnDismissListener listener) { | 
|  | mOnDismissListener = listener; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the initial count of items shown in the activities popup | 
|  | * i.e. the items before the popup is expanded. This is an upper | 
|  | * bound since it is not guaranteed that such number of intent | 
|  | * handlers exist. | 
|  | * | 
|  | * @param itemCount The initial popup item count. | 
|  | */ | 
|  | public void setInitialActivityCount(int itemCount) { | 
|  | mInitialActivityCount = itemCount; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets a content description of the default action button. This | 
|  | * resource should be a string taking one formatting argument and | 
|  | * will be used for formatting the content description of the button | 
|  | * dynamically as the default target changes. For example, a resource | 
|  | * pointing to the string "share with %1$s" will result in a content | 
|  | * description "share with Bluetooth" for the Bluetooth activity. | 
|  | * | 
|  | * @param resourceId The resource id. | 
|  | */ | 
|  | public void setDefaultActionButtonContentDescription(@StringRes int resourceId) { | 
|  | mDefaultActionButtonContentDescription = resourceId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the list popup window which is lazily initialized. | 
|  | * | 
|  | * @return The popup. | 
|  | */ | 
|  | private ListPopupWindow getListPopupWindow() { | 
|  | if (mListPopupWindow == null) { | 
|  | mListPopupWindow = new ListPopupWindow(getContext()); | 
|  | mListPopupWindow.setAdapter(mAdapter); | 
|  | mListPopupWindow.setAnchorView(ActivityChooserView.this); | 
|  | mListPopupWindow.setModal(true); | 
|  | mListPopupWindow.setOnItemClickListener(mCallbacks); | 
|  | mListPopupWindow.setOnDismissListener(mCallbacks); | 
|  | } | 
|  | return mListPopupWindow; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Updates the buttons state. | 
|  | */ | 
|  | private void updateAppearance() { | 
|  | // Expand overflow button. | 
|  | if (mAdapter.getCount() > 0) { | 
|  | mExpandActivityOverflowButton.setEnabled(true); | 
|  | } else { | 
|  | mExpandActivityOverflowButton.setEnabled(false); | 
|  | } | 
|  | // Default activity button. | 
|  | final int activityCount = mAdapter.getActivityCount(); | 
|  | final int historySize = mAdapter.getHistorySize(); | 
|  | if (activityCount==1 || activityCount > 1 && historySize > 0) { | 
|  | mDefaultActivityButton.setVisibility(VISIBLE); | 
|  | ResolveInfo activity = mAdapter.getDefaultActivity(); | 
|  | PackageManager packageManager = mContext.getPackageManager(); | 
|  | mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager)); | 
|  | if (mDefaultActionButtonContentDescription != 0) { | 
|  | CharSequence label = activity.loadLabel(packageManager); | 
|  | String contentDescription = mContext.getString( | 
|  | mDefaultActionButtonContentDescription, label); | 
|  | mDefaultActivityButton.setContentDescription(contentDescription); | 
|  | } | 
|  | } else { | 
|  | mDefaultActivityButton.setVisibility(View.GONE); | 
|  | } | 
|  | // Activity chooser content. | 
|  | if (mDefaultActivityButton.getVisibility() == VISIBLE) { | 
|  | mActivityChooserContent.setBackground(mActivityChooserContentBackground); | 
|  | } else { | 
|  | mActivityChooserContent.setBackground(null); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Interface implementation to avoid publishing them in the APIs. | 
|  | */ | 
|  | private class Callbacks implements AdapterView.OnItemClickListener, | 
|  | View.OnClickListener, View.OnLongClickListener, PopupWindow.OnDismissListener { | 
|  |  | 
|  | // AdapterView#OnItemClickListener | 
|  | public void onItemClick(AdapterView<?> parent, View view, int position, long id) { | 
|  | ActivityChooserViewAdapter adapter = (ActivityChooserViewAdapter) parent.getAdapter(); | 
|  | final int itemViewType = adapter.getItemViewType(position); | 
|  | switch (itemViewType) { | 
|  | case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_FOOTER: { | 
|  | showPopupUnchecked(ActivityChooserViewAdapter.MAX_ACTIVITY_COUNT_UNLIMITED); | 
|  | } break; | 
|  | case ActivityChooserViewAdapter.ITEM_VIEW_TYPE_ACTIVITY: { | 
|  | dismissPopup(); | 
|  | if (mIsSelectingDefaultActivity) { | 
|  | // The item at position zero is the default already. | 
|  | if (position > 0) { | 
|  | mAdapter.getDataModel().setDefaultActivity(position); | 
|  | } | 
|  | } else { | 
|  | // If the default target is not shown in the list, the first | 
|  | // item in the model is default action => adjust index | 
|  | position = mAdapter.getShowDefaultActivity() ? position : position + 1; | 
|  | Intent launchIntent = mAdapter.getDataModel().chooseActivity(position); | 
|  | if (launchIntent != null) { | 
|  | launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | 
|  | ResolveInfo resolveInfo = mAdapter.getDataModel().getActivity(position); | 
|  | startActivity(launchIntent, resolveInfo); | 
|  | } | 
|  | } | 
|  | } break; | 
|  | default: | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // View.OnClickListener | 
|  | public void onClick(View view) { | 
|  | if (view == mDefaultActivityButton) { | 
|  | dismissPopup(); | 
|  | ResolveInfo defaultActivity = mAdapter.getDefaultActivity(); | 
|  | final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity); | 
|  | Intent launchIntent = mAdapter.getDataModel().chooseActivity(index); | 
|  | if (launchIntent != null) { | 
|  | launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | 
|  | startActivity(launchIntent, defaultActivity); | 
|  | } | 
|  | } else if (view == mExpandActivityOverflowButton) { | 
|  | mIsSelectingDefaultActivity = false; | 
|  | showPopupUnchecked(mInitialActivityCount); | 
|  | } else { | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // OnLongClickListener#onLongClick | 
|  | @Override | 
|  | public boolean onLongClick(View view) { | 
|  | if (view == mDefaultActivityButton) { | 
|  | if (mAdapter.getCount() > 0) { | 
|  | mIsSelectingDefaultActivity = true; | 
|  | showPopupUnchecked(mInitialActivityCount); | 
|  | } | 
|  | } else { | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // PopUpWindow.OnDismissListener#onDismiss | 
|  | public void onDismiss() { | 
|  | notifyOnDismissListener(); | 
|  | if (mProvider != null) { | 
|  | mProvider.subUiVisibilityChanged(false); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void notifyOnDismissListener() { | 
|  | if (mOnDismissListener != null) { | 
|  | mOnDismissListener.onDismiss(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void startActivity(Intent intent, ResolveInfo resolveInfo) { | 
|  | try { | 
|  | mContext.startActivity(intent); | 
|  | } catch (RuntimeException re) { | 
|  | CharSequence appLabel = resolveInfo.loadLabel(mContext.getPackageManager()); | 
|  | String message = mContext.getString( | 
|  | R.string.activitychooserview_choose_application_error, appLabel); | 
|  | Log.e(LOG_TAG, message); | 
|  | Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Adapter for backing the list of activities shown in the popup. | 
|  | */ | 
|  | private class ActivityChooserViewAdapter extends BaseAdapter { | 
|  |  | 
|  | public static final int MAX_ACTIVITY_COUNT_UNLIMITED = Integer.MAX_VALUE; | 
|  |  | 
|  | public static final int MAX_ACTIVITY_COUNT_DEFAULT = 4; | 
|  |  | 
|  | private static final int ITEM_VIEW_TYPE_ACTIVITY = 0; | 
|  |  | 
|  | private static final int ITEM_VIEW_TYPE_FOOTER = 1; | 
|  |  | 
|  | private static final int ITEM_VIEW_TYPE_COUNT = 3; | 
|  |  | 
|  | private ActivityChooserModel mDataModel; | 
|  |  | 
|  | private int mMaxActivityCount = MAX_ACTIVITY_COUNT_DEFAULT; | 
|  |  | 
|  | private boolean mShowDefaultActivity; | 
|  |  | 
|  | private boolean mHighlightDefaultActivity; | 
|  |  | 
|  | private boolean mShowFooterView; | 
|  |  | 
|  | public void setDataModel(ActivityChooserModel dataModel) { | 
|  | ActivityChooserModel oldDataModel = mAdapter.getDataModel(); | 
|  | if (oldDataModel != null && isShown()) { | 
|  | oldDataModel.unregisterObserver(mModelDataSetOberver); | 
|  | } | 
|  | mDataModel = dataModel; | 
|  | if (dataModel != null && isShown()) { | 
|  | dataModel.registerObserver(mModelDataSetOberver); | 
|  | } | 
|  | notifyDataSetChanged(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getItemViewType(int position) { | 
|  | if (mShowFooterView && position == getCount() - 1) { | 
|  | return ITEM_VIEW_TYPE_FOOTER; | 
|  | } else { | 
|  | return ITEM_VIEW_TYPE_ACTIVITY; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getViewTypeCount() { | 
|  | return ITEM_VIEW_TYPE_COUNT; | 
|  | } | 
|  |  | 
|  | public int getCount() { | 
|  | int count = 0; | 
|  | int activityCount = mDataModel.getActivityCount(); | 
|  | if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) { | 
|  | activityCount--; | 
|  | } | 
|  | count = Math.min(activityCount, mMaxActivityCount); | 
|  | if (mShowFooterView) { | 
|  | count++; | 
|  | } | 
|  | return count; | 
|  | } | 
|  |  | 
|  | public Object getItem(int position) { | 
|  | final int itemViewType = getItemViewType(position); | 
|  | switch (itemViewType) { | 
|  | case ITEM_VIEW_TYPE_FOOTER: | 
|  | return null; | 
|  | case ITEM_VIEW_TYPE_ACTIVITY: | 
|  | if (!mShowDefaultActivity && mDataModel.getDefaultActivity() != null) { | 
|  | position++; | 
|  | } | 
|  | return mDataModel.getActivity(position); | 
|  | default: | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public long getItemId(int position) { | 
|  | return position; | 
|  | } | 
|  |  | 
|  | public View getView(int position, View convertView, ViewGroup parent) { | 
|  | final int itemViewType = getItemViewType(position); | 
|  | switch (itemViewType) { | 
|  | case ITEM_VIEW_TYPE_FOOTER: | 
|  | if (convertView == null || convertView.getId() != ITEM_VIEW_TYPE_FOOTER) { | 
|  | convertView = LayoutInflater.from(getContext()).inflate( | 
|  | R.layout.activity_chooser_view_list_item, parent, false); | 
|  | convertView.setId(ITEM_VIEW_TYPE_FOOTER); | 
|  | TextView titleView = convertView.findViewById(R.id.title); | 
|  | titleView.setText(mContext.getString( | 
|  | R.string.activity_chooser_view_see_all)); | 
|  | } | 
|  | return convertView; | 
|  | case ITEM_VIEW_TYPE_ACTIVITY: | 
|  | if (convertView == null || convertView.getId() != R.id.list_item) { | 
|  | convertView = LayoutInflater.from(getContext()).inflate( | 
|  | R.layout.activity_chooser_view_list_item, parent, false); | 
|  | } | 
|  | PackageManager packageManager = mContext.getPackageManager(); | 
|  | // Set the icon | 
|  | ImageView iconView = convertView.findViewById(R.id.icon); | 
|  | ResolveInfo activity = (ResolveInfo) getItem(position); | 
|  | iconView.setImageDrawable(activity.loadIcon(packageManager)); | 
|  | // Set the title. | 
|  | TextView titleView = convertView.findViewById(R.id.title); | 
|  | titleView.setText(activity.loadLabel(packageManager)); | 
|  | // Highlight the default. | 
|  | if (mShowDefaultActivity && position == 0 && mHighlightDefaultActivity) { | 
|  | convertView.setActivated(true); | 
|  | } else { | 
|  | convertView.setActivated(false); | 
|  | } | 
|  | return convertView; | 
|  | default: | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public int measureContentWidth() { | 
|  | // The user may have specified some of the target not to be shown but we | 
|  | // want to measure all of them since after expansion they should fit. | 
|  | final int oldMaxActivityCount = mMaxActivityCount; | 
|  | mMaxActivityCount = MAX_ACTIVITY_COUNT_UNLIMITED; | 
|  |  | 
|  | int contentWidth = 0; | 
|  | View itemView = null; | 
|  |  | 
|  | final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | 
|  | final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | 
|  | final int count = getCount(); | 
|  |  | 
|  | for (int i = 0; i < count; i++) { | 
|  | itemView = getView(i, itemView, null); | 
|  | itemView.measure(widthMeasureSpec, heightMeasureSpec); | 
|  | contentWidth = Math.max(contentWidth, itemView.getMeasuredWidth()); | 
|  | } | 
|  |  | 
|  | mMaxActivityCount = oldMaxActivityCount; | 
|  |  | 
|  | return contentWidth; | 
|  | } | 
|  |  | 
|  | public void setMaxActivityCount(int maxActivityCount) { | 
|  | if (mMaxActivityCount != maxActivityCount) { | 
|  | mMaxActivityCount = maxActivityCount; | 
|  | notifyDataSetChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public ResolveInfo getDefaultActivity() { | 
|  | return mDataModel.getDefaultActivity(); | 
|  | } | 
|  |  | 
|  | public void setShowFooterView(boolean showFooterView) { | 
|  | if (mShowFooterView != showFooterView) { | 
|  | mShowFooterView = showFooterView; | 
|  | notifyDataSetChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public int getActivityCount() { | 
|  | return mDataModel.getActivityCount(); | 
|  | } | 
|  |  | 
|  | public int getHistorySize() { | 
|  | return mDataModel.getHistorySize(); | 
|  | } | 
|  |  | 
|  | public ActivityChooserModel getDataModel() { | 
|  | return mDataModel; | 
|  | } | 
|  |  | 
|  | public void setShowDefaultActivity(boolean showDefaultActivity, | 
|  | boolean highlightDefaultActivity) { | 
|  | if (mShowDefaultActivity != showDefaultActivity | 
|  | || mHighlightDefaultActivity != highlightDefaultActivity) { | 
|  | mShowDefaultActivity = showDefaultActivity; | 
|  | mHighlightDefaultActivity = highlightDefaultActivity; | 
|  | notifyDataSetChanged(); | 
|  | } | 
|  | } | 
|  |  | 
|  | public boolean getShowDefaultActivity() { | 
|  | return mShowDefaultActivity; | 
|  | } | 
|  | } | 
|  | } |