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