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