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