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