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