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