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