1/* 2 * Copyright (C) 2015 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 17package android.support.v7.app; 18 19import android.content.Context; 20import android.content.DialogInterface; 21import android.content.res.TypedArray; 22import android.database.Cursor; 23import android.graphics.drawable.Drawable; 24import android.os.Build; 25import android.os.Handler; 26import android.os.Message; 27import android.support.annotation.Nullable; 28import android.support.v4.view.ViewCompat; 29import android.support.v4.widget.NestedScrollView; 30import android.support.v7.appcompat.R; 31import android.text.TextUtils; 32import android.util.TypedValue; 33import android.view.KeyEvent; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.ViewGroup.LayoutParams; 38import android.view.ViewParent; 39import android.view.ViewStub; 40import android.view.Window; 41import android.view.WindowManager; 42import android.widget.AbsListView; 43import android.widget.AdapterView; 44import android.widget.AdapterView.OnItemClickListener; 45import android.widget.ArrayAdapter; 46import android.widget.Button; 47import android.widget.CheckedTextView; 48import android.widget.CursorAdapter; 49import android.widget.FrameLayout; 50import android.widget.ImageView; 51import android.widget.LinearLayout; 52import android.widget.ListAdapter; 53import android.widget.ListView; 54import android.widget.SimpleCursorAdapter; 55import android.widget.TextView; 56 57import java.lang.ref.WeakReference; 58 59import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 60 61class AlertController { 62 private final Context mContext; 63 private final AppCompatDialog mDialog; 64 private final Window mWindow; 65 66 private CharSequence mTitle; 67 private CharSequence mMessage; 68 private ListView mListView; 69 private View mView; 70 71 private int mViewLayoutResId; 72 73 private int mViewSpacingLeft; 74 private int mViewSpacingTop; 75 private int mViewSpacingRight; 76 private int mViewSpacingBottom; 77 private boolean mViewSpacingSpecified = false; 78 79 private Button mButtonPositive; 80 private CharSequence mButtonPositiveText; 81 private Message mButtonPositiveMessage; 82 83 private Button mButtonNegative; 84 private CharSequence mButtonNegativeText; 85 private Message mButtonNegativeMessage; 86 87 private Button mButtonNeutral; 88 private CharSequence mButtonNeutralText; 89 private Message mButtonNeutralMessage; 90 91 private NestedScrollView mScrollView; 92 93 private int mIconId = 0; 94 private Drawable mIcon; 95 96 private ImageView mIconView; 97 private TextView mTitleView; 98 private TextView mMessageView; 99 private View mCustomTitleView; 100 101 private ListAdapter mAdapter; 102 103 private int mCheckedItem = -1; 104 105 private int mAlertDialogLayout; 106 private int mButtonPanelSideLayout; 107 private int mListLayout; 108 private int mMultiChoiceItemLayout; 109 private int mSingleChoiceItemLayout; 110 private int mListItemLayout; 111 112 private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE; 113 114 private Handler mHandler; 115 116 private final View.OnClickListener mButtonHandler = new View.OnClickListener() { 117 @Override 118 public void onClick(View v) { 119 final Message m; 120 if (v == mButtonPositive && mButtonPositiveMessage != null) { 121 m = Message.obtain(mButtonPositiveMessage); 122 } else if (v == mButtonNegative && mButtonNegativeMessage != null) { 123 m = Message.obtain(mButtonNegativeMessage); 124 } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { 125 m = Message.obtain(mButtonNeutralMessage); 126 } else { 127 m = null; 128 } 129 130 if (m != null) { 131 m.sendToTarget(); 132 } 133 134 // Post a message so we dismiss after the above handlers are executed 135 mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog) 136 .sendToTarget(); 137 } 138 }; 139 140 private static final class ButtonHandler extends Handler { 141 // Button clicks have Message.what as the BUTTON{1,2,3} constant 142 private static final int MSG_DISMISS_DIALOG = 1; 143 144 private WeakReference<DialogInterface> mDialog; 145 146 public ButtonHandler(DialogInterface dialog) { 147 mDialog = new WeakReference<>(dialog); 148 } 149 150 @Override 151 public void handleMessage(Message msg) { 152 switch (msg.what) { 153 154 case DialogInterface.BUTTON_POSITIVE: 155 case DialogInterface.BUTTON_NEGATIVE: 156 case DialogInterface.BUTTON_NEUTRAL: 157 ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what); 158 break; 159 160 case MSG_DISMISS_DIALOG: 161 ((DialogInterface) msg.obj).dismiss(); 162 } 163 } 164 } 165 166 public AlertController(Context context, AppCompatDialog di, Window window) { 167 mContext = context; 168 mDialog = di; 169 mWindow = window; 170 mHandler = new ButtonHandler(di); 171 172 final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog, 173 R.attr.alertDialogStyle, 0); 174 175 mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0); 176 mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0); 177 178 mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0); 179 mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0); 180 mSingleChoiceItemLayout = a 181 .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0); 182 mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0); 183 184 a.recycle(); 185 186 /* We use a custom title so never request a window title */ 187 di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 188 } 189 190 static boolean canTextInput(View v) { 191 if (v.onCheckIsTextEditor()) { 192 return true; 193 } 194 195 if (!(v instanceof ViewGroup)) { 196 return false; 197 } 198 199 ViewGroup vg = (ViewGroup) v; 200 int i = vg.getChildCount(); 201 while (i > 0) { 202 i--; 203 v = vg.getChildAt(i); 204 if (canTextInput(v)) { 205 return true; 206 } 207 } 208 209 return false; 210 } 211 212 public void installContent() { 213 final int contentView = selectContentView(); 214 mDialog.setContentView(contentView); 215 setupView(); 216 } 217 218 private int selectContentView() { 219 if (mButtonPanelSideLayout == 0) { 220 return mAlertDialogLayout; 221 } 222 if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) { 223 return mButtonPanelSideLayout; 224 } 225 return mAlertDialogLayout; 226 } 227 228 public void setTitle(CharSequence title) { 229 mTitle = title; 230 if (mTitleView != null) { 231 mTitleView.setText(title); 232 } 233 } 234 235 /** 236 * @see AlertDialog.Builder#setCustomTitle(View) 237 */ 238 public void setCustomTitle(View customTitleView) { 239 mCustomTitleView = customTitleView; 240 } 241 242 public void setMessage(CharSequence message) { 243 mMessage = message; 244 if (mMessageView != null) { 245 mMessageView.setText(message); 246 } 247 } 248 249 /** 250 * Set the view resource to display in the dialog. 251 */ 252 public void setView(int layoutResId) { 253 mView = null; 254 mViewLayoutResId = layoutResId; 255 mViewSpacingSpecified = false; 256 } 257 258 /** 259 * Set the view to display in the dialog. 260 */ 261 public void setView(View view) { 262 mView = view; 263 mViewLayoutResId = 0; 264 mViewSpacingSpecified = false; 265 } 266 267 /** 268 * Set the view to display in the dialog along with the spacing around that view 269 */ 270 public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, 271 int viewSpacingBottom) { 272 mView = view; 273 mViewLayoutResId = 0; 274 mViewSpacingSpecified = true; 275 mViewSpacingLeft = viewSpacingLeft; 276 mViewSpacingTop = viewSpacingTop; 277 mViewSpacingRight = viewSpacingRight; 278 mViewSpacingBottom = viewSpacingBottom; 279 } 280 281 /** 282 * Sets a hint for the best button panel layout. 283 */ 284 public void setButtonPanelLayoutHint(int layoutHint) { 285 mButtonPanelLayoutHint = layoutHint; 286 } 287 288 /** 289 * Sets a click listener or a message to be sent when the button is clicked. 290 * You only need to pass one of {@code listener} or {@code msg}. 291 * 292 * @param whichButton Which button, can be one of 293 * {@link DialogInterface#BUTTON_POSITIVE}, 294 * {@link DialogInterface#BUTTON_NEGATIVE}, or 295 * {@link DialogInterface#BUTTON_NEUTRAL} 296 * @param text The text to display in positive button. 297 * @param listener The {@link DialogInterface.OnClickListener} to use. 298 * @param msg The {@link Message} to be sent when clicked. 299 */ 300 public void setButton(int whichButton, CharSequence text, 301 DialogInterface.OnClickListener listener, Message msg) { 302 303 if (msg == null && listener != null) { 304 msg = mHandler.obtainMessage(whichButton, listener); 305 } 306 307 switch (whichButton) { 308 309 case DialogInterface.BUTTON_POSITIVE: 310 mButtonPositiveText = text; 311 mButtonPositiveMessage = msg; 312 break; 313 314 case DialogInterface.BUTTON_NEGATIVE: 315 mButtonNegativeText = text; 316 mButtonNegativeMessage = msg; 317 break; 318 319 case DialogInterface.BUTTON_NEUTRAL: 320 mButtonNeutralText = text; 321 mButtonNeutralMessage = msg; 322 break; 323 324 default: 325 throw new IllegalArgumentException("Button does not exist"); 326 } 327 } 328 329 /** 330 * Specifies the icon to display next to the alert title. 331 * 332 * @param resId the resource identifier of the drawable to use as the icon, 333 * or 0 for no icon 334 */ 335 public void setIcon(int resId) { 336 mIcon = null; 337 mIconId = resId; 338 339 if (mIconView != null) { 340 if (resId != 0) { 341 mIconView.setVisibility(View.VISIBLE); 342 mIconView.setImageResource(mIconId); 343 } else { 344 mIconView.setVisibility(View.GONE); 345 } 346 } 347 } 348 349 /** 350 * Specifies the icon to display next to the alert title. 351 * 352 * @param icon the drawable to use as the icon or null for no icon 353 */ 354 public void setIcon(Drawable icon) { 355 mIcon = icon; 356 mIconId = 0; 357 358 if (mIconView != null) { 359 if (icon != null) { 360 mIconView.setVisibility(View.VISIBLE); 361 mIconView.setImageDrawable(icon); 362 } else { 363 mIconView.setVisibility(View.GONE); 364 } 365 } 366 } 367 368 /** 369 * @param attrId the attributeId of the theme-specific drawable 370 * to resolve the resourceId for. 371 * 372 * @return resId the resourceId of the theme-specific drawable 373 */ 374 public int getIconAttributeResId(int attrId) { 375 TypedValue out = new TypedValue(); 376 mContext.getTheme().resolveAttribute(attrId, out, true); 377 return out.resourceId; 378 } 379 380 public ListView getListView() { 381 return mListView; 382 } 383 384 public Button getButton(int whichButton) { 385 switch (whichButton) { 386 case DialogInterface.BUTTON_POSITIVE: 387 return mButtonPositive; 388 case DialogInterface.BUTTON_NEGATIVE: 389 return mButtonNegative; 390 case DialogInterface.BUTTON_NEUTRAL: 391 return mButtonNeutral; 392 default: 393 return null; 394 } 395 } 396 397 @SuppressWarnings({"UnusedDeclaration"}) 398 public boolean onKeyDown(int keyCode, KeyEvent event) { 399 return mScrollView != null && mScrollView.executeKeyEvent(event); 400 } 401 402 @SuppressWarnings({"UnusedDeclaration"}) 403 public boolean onKeyUp(int keyCode, KeyEvent event) { 404 return mScrollView != null && mScrollView.executeKeyEvent(event); 405 } 406 407 /** 408 * Resolves whether a custom or default panel should be used. Removes the 409 * default panel if a custom panel should be used. If the resolved panel is 410 * a view stub, inflates before returning. 411 * 412 * @param customPanel the custom panel 413 * @param defaultPanel the default panel 414 * @return the panel to use 415 */ 416 @Nullable 417 private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) { 418 if (customPanel == null) { 419 // Inflate the default panel, if needed. 420 if (defaultPanel instanceof ViewStub) { 421 defaultPanel = ((ViewStub) defaultPanel).inflate(); 422 } 423 424 return (ViewGroup) defaultPanel; 425 } 426 427 // Remove the default panel entirely. 428 if (defaultPanel != null) { 429 final ViewParent parent = defaultPanel.getParent(); 430 if (parent instanceof ViewGroup) { 431 ((ViewGroup) parent).removeView(defaultPanel); 432 } 433 } 434 435 // Inflate the custom panel, if needed. 436 if (customPanel instanceof ViewStub) { 437 customPanel = ((ViewStub) customPanel).inflate(); 438 } 439 440 return (ViewGroup) customPanel; 441 } 442 443 private void setupView() { 444 final View parentPanel = mWindow.findViewById(R.id.parentPanel); 445 final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel); 446 final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel); 447 final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel); 448 449 // Install custom content before setting up the title or buttons so 450 // that we can handle panel overrides. 451 final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel); 452 setupCustomContent(customPanel); 453 454 final View customTopPanel = customPanel.findViewById(R.id.topPanel); 455 final View customContentPanel = customPanel.findViewById(R.id.contentPanel); 456 final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel); 457 458 // Resolve the correct panels and remove the defaults, if needed. 459 final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel); 460 final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel); 461 final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel); 462 463 setupContent(contentPanel); 464 setupButtons(buttonPanel); 465 setupTitle(topPanel); 466 467 final boolean hasCustomPanel = customPanel != null 468 && customPanel.getVisibility() != View.GONE; 469 final boolean hasTopPanel = topPanel != null 470 && topPanel.getVisibility() != View.GONE; 471 final boolean hasButtonPanel = buttonPanel != null 472 && buttonPanel.getVisibility() != View.GONE; 473 474 // Only display the text spacer if we don't have buttons. 475 if (!hasButtonPanel) { 476 if (contentPanel != null) { 477 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons); 478 if (spacer != null) { 479 spacer.setVisibility(View.VISIBLE); 480 } 481 } 482 } 483 484 if (hasTopPanel) { 485 // Only clip scrolling content to padding if we have a title. 486 if (mScrollView != null) { 487 mScrollView.setClipToPadding(true); 488 } 489 } 490 491 // Update scroll indicators as needed. 492 if (!hasCustomPanel) { 493 final View content = mListView != null ? mListView : mScrollView; 494 if (content != null) { 495 final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0) 496 | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0); 497 setScrollIndicators(contentPanel, content, indicators, 498 ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM); 499 } 500 } 501 502 final ListView listView = mListView; 503 if (listView != null && mAdapter != null) { 504 listView.setAdapter(mAdapter); 505 final int checkedItem = mCheckedItem; 506 if (checkedItem > -1) { 507 listView.setItemChecked(checkedItem, true); 508 listView.setSelection(checkedItem); 509 } 510 } 511 } 512 513 private void setScrollIndicators(ViewGroup contentPanel, View content, 514 final int indicators, final int mask) { 515 // Set up scroll indicators (if present). 516 View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp); 517 View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown); 518 519 if (Build.VERSION.SDK_INT >= 23) { 520 // We're on Marshmallow so can rely on the View APIs 521 ViewCompat.setScrollIndicators(content, indicators, mask); 522 // We can also remove the compat indicator views 523 if (indicatorUp != null) { 524 contentPanel.removeView(indicatorUp); 525 } 526 if (indicatorDown != null) { 527 contentPanel.removeView(indicatorDown); 528 } 529 } else { 530 // First, remove the indicator views if we're not set to use them 531 if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) { 532 contentPanel.removeView(indicatorUp); 533 indicatorUp = null; 534 } 535 if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) { 536 contentPanel.removeView(indicatorDown); 537 indicatorDown = null; 538 } 539 540 if (indicatorUp != null || indicatorDown != null) { 541 final View top = indicatorUp; 542 final View bottom = indicatorDown; 543 544 if (mMessage != null) { 545 // We're just showing the ScrollView, set up listener. 546 mScrollView.setOnScrollChangeListener( 547 new NestedScrollView.OnScrollChangeListener() { 548 @Override 549 public void onScrollChange(NestedScrollView v, int scrollX, 550 int scrollY, 551 int oldScrollX, int oldScrollY) { 552 manageScrollIndicators(v, top, bottom); 553 } 554 }); 555 // Set up the indicators following layout. 556 mScrollView.post(new Runnable() { 557 @Override 558 public void run() { 559 manageScrollIndicators(mScrollView, top, bottom); 560 } 561 }); 562 } else if (mListView != null) { 563 // We're just showing the AbsListView, set up listener. 564 mListView.setOnScrollListener(new AbsListView.OnScrollListener() { 565 @Override 566 public void onScrollStateChanged(AbsListView view, int scrollState) {} 567 568 @Override 569 public void onScroll(AbsListView v, int firstVisibleItem, 570 int visibleItemCount, int totalItemCount) { 571 manageScrollIndicators(v, top, bottom); 572 } 573 }); 574 // Set up the indicators following layout. 575 mListView.post(new Runnable() { 576 @Override 577 public void run() { 578 manageScrollIndicators(mListView, top, bottom); 579 } 580 }); 581 } else { 582 // We don't have any content to scroll, remove the indicators. 583 if (top != null) { 584 contentPanel.removeView(top); 585 } 586 if (bottom != null) { 587 contentPanel.removeView(bottom); 588 } 589 } 590 } 591 } 592 } 593 594 private void setupCustomContent(ViewGroup customPanel) { 595 final View customView; 596 if (mView != null) { 597 customView = mView; 598 } else if (mViewLayoutResId != 0) { 599 final LayoutInflater inflater = LayoutInflater.from(mContext); 600 customView = inflater.inflate(mViewLayoutResId, customPanel, false); 601 } else { 602 customView = null; 603 } 604 605 final boolean hasCustomView = customView != null; 606 if (!hasCustomView || !canTextInput(customView)) { 607 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 608 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 609 } 610 611 if (hasCustomView) { 612 final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); 613 custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 614 615 if (mViewSpacingSpecified) { 616 custom.setPadding( 617 mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); 618 } 619 620 if (mListView != null) { 621 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; 622 } 623 } else { 624 customPanel.setVisibility(View.GONE); 625 } 626 } 627 628 private void setupTitle(ViewGroup topPanel) { 629 if (mCustomTitleView != null) { 630 // Add the custom title view directly to the topPanel layout 631 LayoutParams lp = new LayoutParams( 632 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 633 634 topPanel.addView(mCustomTitleView, 0, lp); 635 636 // Hide the title template 637 View titleTemplate = mWindow.findViewById(R.id.title_template); 638 titleTemplate.setVisibility(View.GONE); 639 } else { 640 mIconView = (ImageView) mWindow.findViewById(android.R.id.icon); 641 642 final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); 643 if (hasTextTitle) { 644 // Display the title if a title is supplied, else hide it. 645 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); 646 mTitleView.setText(mTitle); 647 648 // Do this last so that if the user has supplied any icons we 649 // use them instead of the default ones. If the user has 650 // specified 0 then make it disappear. 651 if (mIconId != 0) { 652 mIconView.setImageResource(mIconId); 653 } else if (mIcon != null) { 654 mIconView.setImageDrawable(mIcon); 655 } else { 656 // Apply the padding from the icon to ensure the title is 657 // aligned correctly. 658 mTitleView.setPadding(mIconView.getPaddingLeft(), 659 mIconView.getPaddingTop(), 660 mIconView.getPaddingRight(), 661 mIconView.getPaddingBottom()); 662 mIconView.setVisibility(View.GONE); 663 } 664 } else { 665 // Hide the title template 666 final View titleTemplate = mWindow.findViewById(R.id.title_template); 667 titleTemplate.setVisibility(View.GONE); 668 mIconView.setVisibility(View.GONE); 669 topPanel.setVisibility(View.GONE); 670 } 671 } 672 } 673 674 private void setupContent(ViewGroup contentPanel) { 675 mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView); 676 mScrollView.setFocusable(false); 677 mScrollView.setNestedScrollingEnabled(false); 678 679 // Special case for users that only want to display a String 680 mMessageView = (TextView) contentPanel.findViewById(android.R.id.message); 681 if (mMessageView == null) { 682 return; 683 } 684 685 if (mMessage != null) { 686 mMessageView.setText(mMessage); 687 } else { 688 mMessageView.setVisibility(View.GONE); 689 mScrollView.removeView(mMessageView); 690 691 if (mListView != null) { 692 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent(); 693 final int childIndex = scrollParent.indexOfChild(mScrollView); 694 scrollParent.removeViewAt(childIndex); 695 scrollParent.addView(mListView, childIndex, 696 new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 697 } else { 698 contentPanel.setVisibility(View.GONE); 699 } 700 } 701 } 702 703 private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) { 704 if (upIndicator != null) { 705 upIndicator.setVisibility( 706 ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE); 707 } 708 if (downIndicator != null) { 709 downIndicator.setVisibility( 710 ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE); 711 } 712 } 713 714 private void setupButtons(ViewGroup buttonPanel) { 715 int BIT_BUTTON_POSITIVE = 1; 716 int BIT_BUTTON_NEGATIVE = 2; 717 int BIT_BUTTON_NEUTRAL = 4; 718 int whichButtons = 0; 719 mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1); 720 mButtonPositive.setOnClickListener(mButtonHandler); 721 722 if (TextUtils.isEmpty(mButtonPositiveText)) { 723 mButtonPositive.setVisibility(View.GONE); 724 } else { 725 mButtonPositive.setText(mButtonPositiveText); 726 mButtonPositive.setVisibility(View.VISIBLE); 727 whichButtons = whichButtons | BIT_BUTTON_POSITIVE; 728 } 729 730 mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2); 731 mButtonNegative.setOnClickListener(mButtonHandler); 732 733 if (TextUtils.isEmpty(mButtonNegativeText)) { 734 mButtonNegative.setVisibility(View.GONE); 735 } else { 736 mButtonNegative.setText(mButtonNegativeText); 737 mButtonNegative.setVisibility(View.VISIBLE); 738 739 whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; 740 } 741 742 mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3); 743 mButtonNeutral.setOnClickListener(mButtonHandler); 744 745 if (TextUtils.isEmpty(mButtonNeutralText)) { 746 mButtonNeutral.setVisibility(View.GONE); 747 } else { 748 mButtonNeutral.setText(mButtonNeutralText); 749 mButtonNeutral.setVisibility(View.VISIBLE); 750 751 whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; 752 } 753 754 final boolean hasButtons = whichButtons != 0; 755 if (!hasButtons) { 756 buttonPanel.setVisibility(View.GONE); 757 } 758 } 759 760 public static class AlertParams { 761 public final Context mContext; 762 public final LayoutInflater mInflater; 763 764 public int mIconId = 0; 765 public Drawable mIcon; 766 public int mIconAttrId = 0; 767 public CharSequence mTitle; 768 public View mCustomTitleView; 769 public CharSequence mMessage; 770 public CharSequence mPositiveButtonText; 771 public DialogInterface.OnClickListener mPositiveButtonListener; 772 public CharSequence mNegativeButtonText; 773 public DialogInterface.OnClickListener mNegativeButtonListener; 774 public CharSequence mNeutralButtonText; 775 public DialogInterface.OnClickListener mNeutralButtonListener; 776 public boolean mCancelable; 777 public DialogInterface.OnCancelListener mOnCancelListener; 778 public DialogInterface.OnDismissListener mOnDismissListener; 779 public DialogInterface.OnKeyListener mOnKeyListener; 780 public CharSequence[] mItems; 781 public ListAdapter mAdapter; 782 public DialogInterface.OnClickListener mOnClickListener; 783 public int mViewLayoutResId; 784 public View mView; 785 public int mViewSpacingLeft; 786 public int mViewSpacingTop; 787 public int mViewSpacingRight; 788 public int mViewSpacingBottom; 789 public boolean mViewSpacingSpecified = false; 790 public boolean[] mCheckedItems; 791 public boolean mIsMultiChoice; 792 public boolean mIsSingleChoice; 793 public int mCheckedItem = -1; 794 public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; 795 public Cursor mCursor; 796 public String mLabelColumn; 797 public String mIsCheckedColumn; 798 public boolean mForceInverseBackground; 799 public AdapterView.OnItemSelectedListener mOnItemSelectedListener; 800 public OnPrepareListViewListener mOnPrepareListViewListener; 801 public boolean mRecycleOnMeasure = true; 802 803 /** 804 * Interface definition for a callback to be invoked before the ListView 805 * will be bound to an adapter. 806 */ 807 public interface OnPrepareListViewListener { 808 809 /** 810 * Called before the ListView is bound to an adapter. 811 * @param listView The ListView that will be shown in the dialog. 812 */ 813 void onPrepareListView(ListView listView); 814 } 815 816 public AlertParams(Context context) { 817 mContext = context; 818 mCancelable = true; 819 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 820 } 821 822 public void apply(AlertController dialog) { 823 if (mCustomTitleView != null) { 824 dialog.setCustomTitle(mCustomTitleView); 825 } else { 826 if (mTitle != null) { 827 dialog.setTitle(mTitle); 828 } 829 if (mIcon != null) { 830 dialog.setIcon(mIcon); 831 } 832 if (mIconId != 0) { 833 dialog.setIcon(mIconId); 834 } 835 if (mIconAttrId != 0) { 836 dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); 837 } 838 } 839 if (mMessage != null) { 840 dialog.setMessage(mMessage); 841 } 842 if (mPositiveButtonText != null) { 843 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, 844 mPositiveButtonListener, null); 845 } 846 if (mNegativeButtonText != null) { 847 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, 848 mNegativeButtonListener, null); 849 } 850 if (mNeutralButtonText != null) { 851 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, 852 mNeutralButtonListener, null); 853 } 854 // For a list, the client can either supply an array of items or an 855 // adapter or a cursor 856 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { 857 createListView(dialog); 858 } 859 if (mView != null) { 860 if (mViewSpacingSpecified) { 861 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 862 mViewSpacingBottom); 863 } else { 864 dialog.setView(mView); 865 } 866 } else if (mViewLayoutResId != 0) { 867 dialog.setView(mViewLayoutResId); 868 } 869 870 /* 871 dialog.setCancelable(mCancelable); 872 dialog.setOnCancelListener(mOnCancelListener); 873 if (mOnKeyListener != null) { 874 dialog.setOnKeyListener(mOnKeyListener); 875 } 876 */ 877 } 878 879 private void createListView(final AlertController dialog) { 880 final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null); 881 final ListAdapter adapter; 882 883 if (mIsMultiChoice) { 884 if (mCursor == null) { 885 adapter = new ArrayAdapter<CharSequence>( 886 mContext, dialog.mMultiChoiceItemLayout, android.R.id.text1, mItems) { 887 @Override 888 public View getView(int position, View convertView, ViewGroup parent) { 889 View view = super.getView(position, convertView, parent); 890 if (mCheckedItems != null) { 891 boolean isItemChecked = mCheckedItems[position]; 892 if (isItemChecked) { 893 listView.setItemChecked(position, true); 894 } 895 } 896 return view; 897 } 898 }; 899 } else { 900 adapter = new CursorAdapter(mContext, mCursor, false) { 901 private final int mLabelIndex; 902 private final int mIsCheckedIndex; 903 904 { 905 final Cursor cursor = getCursor(); 906 mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn); 907 mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn); 908 } 909 910 @Override 911 public void bindView(View view, Context context, Cursor cursor) { 912 CheckedTextView text = (CheckedTextView) view.findViewById( 913 android.R.id.text1); 914 text.setText(cursor.getString(mLabelIndex)); 915 listView.setItemChecked(cursor.getPosition(), 916 cursor.getInt(mIsCheckedIndex) == 1); 917 } 918 919 @Override 920 public View newView(Context context, Cursor cursor, ViewGroup parent) { 921 return mInflater.inflate(dialog.mMultiChoiceItemLayout, 922 parent, false); 923 } 924 925 }; 926 } 927 } else { 928 final int layout; 929 if (mIsSingleChoice) { 930 layout = dialog.mSingleChoiceItemLayout; 931 } else { 932 layout = dialog.mListItemLayout; 933 } 934 935 if (mCursor != null) { 936 adapter = new SimpleCursorAdapter(mContext, layout, mCursor, 937 new String[] { mLabelColumn }, new int[] { android.R.id.text1 }); 938 } else if (mAdapter != null) { 939 adapter = mAdapter; 940 } else { 941 adapter = new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems); 942 } 943 } 944 945 if (mOnPrepareListViewListener != null) { 946 mOnPrepareListViewListener.onPrepareListView(listView); 947 } 948 949 /* Don't directly set the adapter on the ListView as we might 950 * want to add a footer to the ListView later. 951 */ 952 dialog.mAdapter = adapter; 953 dialog.mCheckedItem = mCheckedItem; 954 955 if (mOnClickListener != null) { 956 listView.setOnItemClickListener(new OnItemClickListener() { 957 @Override 958 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 959 mOnClickListener.onClick(dialog.mDialog, position); 960 if (!mIsSingleChoice) { 961 dialog.mDialog.dismiss(); 962 } 963 } 964 }); 965 } else if (mOnCheckboxClickListener != null) { 966 listView.setOnItemClickListener(new OnItemClickListener() { 967 @Override 968 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 969 if (mCheckedItems != null) { 970 mCheckedItems[position] = listView.isItemChecked(position); 971 } 972 mOnCheckboxClickListener.onClick( 973 dialog.mDialog, position, listView.isItemChecked(position)); 974 } 975 }); 976 } 977 978 // Attach a given OnItemSelectedListener to the ListView 979 if (mOnItemSelectedListener != null) { 980 listView.setOnItemSelectedListener(mOnItemSelectedListener); 981 } 982 983 if (mIsSingleChoice) { 984 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 985 } else if (mIsMultiChoice) { 986 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 987 } 988 dialog.mListView = listView; 989 } 990 } 991 992 private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { 993 public CheckedItemAdapter(Context context, int resource, int textViewResourceId, 994 CharSequence[] objects) { 995 super(context, resource, textViewResourceId, objects); 996 } 997 998 @Override 999 public boolean hasStableIds() { 1000 return true; 1001 } 1002 1003 @Override 1004 public long getItemId(int position) { 1005 return position; 1006 } 1007 } 1008} 1009