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