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