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