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