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