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