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