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