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