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