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.annotation.Nullable; 24import android.app.AlertDialog; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.res.Configuration; 28import android.content.res.TypedArray; 29import android.database.Cursor; 30import android.graphics.drawable.Drawable; 31import android.os.Handler; 32import android.os.Message; 33import android.text.TextUtils; 34import android.util.AttributeSet; 35import android.util.TypedValue; 36import android.view.Gravity; 37import android.view.KeyEvent; 38import android.view.LayoutInflater; 39import android.view.View; 40import android.view.ViewGroup; 41import android.view.ViewGroup.LayoutParams; 42import android.view.ViewParent; 43import android.view.ViewStub; 44import android.view.Window; 45import android.view.WindowInsets; 46import android.view.WindowManager; 47import android.widget.AdapterView; 48import android.widget.AdapterView.OnItemClickListener; 49import android.widget.ArrayAdapter; 50import android.widget.Button; 51import android.widget.CheckedTextView; 52import android.widget.CursorAdapter; 53import android.widget.FrameLayout; 54import android.widget.ImageView; 55import android.widget.LinearLayout; 56import android.widget.ListAdapter; 57import android.widget.ListView; 58import android.widget.ScrollView; 59import android.widget.SimpleCursorAdapter; 60import android.widget.TextView; 61 62import java.lang.ref.WeakReference; 63 64public class AlertController { 65 public static final int MICRO = 1; 66 67 private final Context mContext; 68 private final DialogInterface mDialogInterface; 69 protected final Window mWindow; 70 71 private CharSequence mTitle; 72 protected CharSequence mMessage; 73 protected ListView mListView; 74 private View mView; 75 76 private int mViewLayoutResId; 77 78 private int mViewSpacingLeft; 79 private int mViewSpacingTop; 80 private int mViewSpacingRight; 81 private int mViewSpacingBottom; 82 private boolean mViewSpacingSpecified = false; 83 84 private Button mButtonPositive; 85 private CharSequence mButtonPositiveText; 86 private Message mButtonPositiveMessage; 87 88 private Button mButtonNegative; 89 private CharSequence mButtonNegativeText; 90 private Message mButtonNegativeMessage; 91 92 private Button mButtonNeutral; 93 private CharSequence mButtonNeutralText; 94 private Message mButtonNeutralMessage; 95 96 protected ScrollView mScrollView; 97 98 private int mIconId = 0; 99 private Drawable mIcon; 100 101 private ImageView mIconView; 102 private TextView mTitleView; 103 protected TextView mMessageView; 104 private View mCustomTitleView; 105 106 private boolean mForceInverseBackground; 107 108 private ListAdapter mAdapter; 109 110 private int mCheckedItem = -1; 111 112 private int mAlertDialogLayout; 113 private int mButtonPanelSideLayout; 114 private int mListLayout; 115 private int mMultiChoiceItemLayout; 116 private int mSingleChoiceItemLayout; 117 private int mListItemLayout; 118 119 private boolean mShowTitle; 120 121 private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE; 122 123 private Handler mHandler; 124 125 private final View.OnClickListener mButtonHandler = new View.OnClickListener() { 126 @Override 127 public void onClick(View v) { 128 final Message m; 129 if (v == mButtonPositive && mButtonPositiveMessage != null) { 130 m = Message.obtain(mButtonPositiveMessage); 131 } else if (v == mButtonNegative && mButtonNegativeMessage != null) { 132 m = Message.obtain(mButtonNegativeMessage); 133 } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { 134 m = Message.obtain(mButtonNeutralMessage); 135 } else { 136 m = null; 137 } 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<>(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 final TypedValue outValue = new TypedValue(); 177 context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, outValue, true); 178 return outValue.data != 0; 179 } 180 181 public static final AlertController create(Context context, DialogInterface di, Window window) { 182 final TypedArray a = context.obtainStyledAttributes( 183 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); 184 int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0); 185 a.recycle(); 186 187 switch (controllerType) { 188 case MICRO: 189 return new MicroAlertController(context, di, window); 190 default: 191 return new AlertController(context, di, window); 192 } 193 } 194 195 protected AlertController(Context context, DialogInterface di, Window window) { 196 mContext = context; 197 mDialogInterface = di; 198 mWindow = window; 199 mHandler = new ButtonHandler(di); 200 201 final TypedArray a = context.obtainStyledAttributes(null, 202 R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); 203 204 mAlertDialogLayout = a.getResourceId( 205 R.styleable.AlertDialog_layout, R.layout.alert_dialog); 206 mButtonPanelSideLayout = a.getResourceId( 207 R.styleable.AlertDialog_buttonPanelSideLayout, 0); 208 mListLayout = a.getResourceId( 209 R.styleable.AlertDialog_listLayout, R.layout.select_dialog); 210 211 mMultiChoiceItemLayout = a.getResourceId( 212 R.styleable.AlertDialog_multiChoiceItemLayout, 213 R.layout.select_dialog_multichoice); 214 mSingleChoiceItemLayout = a.getResourceId( 215 R.styleable.AlertDialog_singleChoiceItemLayout, 216 R.layout.select_dialog_singlechoice); 217 mListItemLayout = a.getResourceId( 218 R.styleable.AlertDialog_listItemLayout, 219 R.layout.select_dialog_item); 220 mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true); 221 222 a.recycle(); 223 224 /* We use a custom title so never request a window title */ 225 window.requestFeature(Window.FEATURE_NO_TITLE); 226 } 227 228 static boolean canTextInput(View v) { 229 if (v.onCheckIsTextEditor()) { 230 return true; 231 } 232 233 if (!(v instanceof ViewGroup)) { 234 return false; 235 } 236 237 ViewGroup vg = (ViewGroup)v; 238 int i = vg.getChildCount(); 239 while (i > 0) { 240 i--; 241 v = vg.getChildAt(i); 242 if (canTextInput(v)) { 243 return true; 244 } 245 } 246 247 return false; 248 } 249 250 public void installContent() { 251 int contentView = selectContentView(); 252 mWindow.setContentView(contentView); 253 setupView(); 254 } 255 256 private int selectContentView() { 257 if (mButtonPanelSideLayout == 0) { 258 return mAlertDialogLayout; 259 } 260 if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) { 261 return mButtonPanelSideLayout; 262 } 263 // TODO: use layout hint side for long messages/lists 264 return mAlertDialogLayout; 265 } 266 267 public void setTitle(CharSequence title) { 268 mTitle = title; 269 if (mTitleView != null) { 270 mTitleView.setText(title); 271 } 272 } 273 274 /** 275 * @see AlertDialog.Builder#setCustomTitle(View) 276 */ 277 public void setCustomTitle(View customTitleView) { 278 mCustomTitleView = customTitleView; 279 } 280 281 public void setMessage(CharSequence message) { 282 mMessage = message; 283 if (mMessageView != null) { 284 mMessageView.setText(message); 285 } 286 } 287 288 /** 289 * Set the view resource to display in the dialog. 290 */ 291 public void setView(int layoutResId) { 292 mView = null; 293 mViewLayoutResId = layoutResId; 294 mViewSpacingSpecified = false; 295 } 296 297 /** 298 * Set the view to display in the dialog. 299 */ 300 public void setView(View view) { 301 mView = view; 302 mViewLayoutResId = 0; 303 mViewSpacingSpecified = false; 304 } 305 306 /** 307 * Set the view to display in the dialog along with the spacing around that view 308 */ 309 public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, 310 int viewSpacingBottom) { 311 mView = view; 312 mViewLayoutResId = 0; 313 mViewSpacingSpecified = true; 314 mViewSpacingLeft = viewSpacingLeft; 315 mViewSpacingTop = viewSpacingTop; 316 mViewSpacingRight = viewSpacingRight; 317 mViewSpacingBottom = viewSpacingBottom; 318 } 319 320 /** 321 * Sets a hint for the best button panel layout. 322 */ 323 public void setButtonPanelLayoutHint(int layoutHint) { 324 mButtonPanelLayoutHint = layoutHint; 325 } 326 327 /** 328 * Sets a click listener or a message to be sent when the button is clicked. 329 * You only need to pass one of {@code listener} or {@code msg}. 330 * 331 * @param whichButton Which button, can be one of 332 * {@link DialogInterface#BUTTON_POSITIVE}, 333 * {@link DialogInterface#BUTTON_NEGATIVE}, or 334 * {@link DialogInterface#BUTTON_NEUTRAL} 335 * @param text The text to display in positive button. 336 * @param listener The {@link DialogInterface.OnClickListener} to use. 337 * @param msg The {@link Message} to be sent when clicked. 338 */ 339 public void setButton(int whichButton, CharSequence text, 340 DialogInterface.OnClickListener listener, Message msg) { 341 342 if (msg == null && listener != null) { 343 msg = mHandler.obtainMessage(whichButton, listener); 344 } 345 346 switch (whichButton) { 347 348 case DialogInterface.BUTTON_POSITIVE: 349 mButtonPositiveText = text; 350 mButtonPositiveMessage = msg; 351 break; 352 353 case DialogInterface.BUTTON_NEGATIVE: 354 mButtonNegativeText = text; 355 mButtonNegativeMessage = msg; 356 break; 357 358 case DialogInterface.BUTTON_NEUTRAL: 359 mButtonNeutralText = text; 360 mButtonNeutralMessage = msg; 361 break; 362 363 default: 364 throw new IllegalArgumentException("Button does not exist"); 365 } 366 } 367 368 /** 369 * Specifies the icon to display next to the alert title. 370 * 371 * @param resId the resource identifier of the drawable to use as the icon, 372 * or 0 for no icon 373 */ 374 public void setIcon(int resId) { 375 mIcon = null; 376 mIconId = resId; 377 378 if (mIconView != null) { 379 if (resId != 0) { 380 mIconView.setVisibility(View.VISIBLE); 381 mIconView.setImageResource(mIconId); 382 } else { 383 mIconView.setVisibility(View.GONE); 384 } 385 } 386 } 387 388 /** 389 * Specifies the icon to display next to the alert title. 390 * 391 * @param icon the drawable to use as the icon or null for no icon 392 */ 393 public void setIcon(Drawable icon) { 394 mIcon = icon; 395 mIconId = 0; 396 397 if (mIconView != null) { 398 if (icon != null) { 399 mIconView.setVisibility(View.VISIBLE); 400 mIconView.setImageDrawable(icon); 401 } else { 402 mIconView.setVisibility(View.GONE); 403 } 404 } 405 } 406 407 /** 408 * @param attrId the attributeId of the theme-specific drawable 409 * to resolve the resourceId for. 410 * 411 * @return resId the resourceId of the theme-specific drawable 412 */ 413 public int getIconAttributeResId(int attrId) { 414 TypedValue out = new TypedValue(); 415 mContext.getTheme().resolveAttribute(attrId, out, true); 416 return out.resourceId; 417 } 418 419 public void setInverseBackgroundForced(boolean forceInverseBackground) { 420 mForceInverseBackground = forceInverseBackground; 421 } 422 423 public ListView getListView() { 424 return mListView; 425 } 426 427 public Button getButton(int whichButton) { 428 switch (whichButton) { 429 case DialogInterface.BUTTON_POSITIVE: 430 return mButtonPositive; 431 case DialogInterface.BUTTON_NEGATIVE: 432 return mButtonNegative; 433 case DialogInterface.BUTTON_NEUTRAL: 434 return mButtonNeutral; 435 default: 436 return null; 437 } 438 } 439 440 @SuppressWarnings({"UnusedDeclaration"}) 441 public boolean onKeyDown(int keyCode, KeyEvent event) { 442 return mScrollView != null && mScrollView.executeKeyEvent(event); 443 } 444 445 @SuppressWarnings({"UnusedDeclaration"}) 446 public boolean onKeyUp(int keyCode, KeyEvent event) { 447 return mScrollView != null && mScrollView.executeKeyEvent(event); 448 } 449 450 /** 451 * Resolves whether a custom or default panel should be used. Removes the 452 * default panel if a custom panel should be used. If the resolved panel is 453 * a view stub, inflates before returning. 454 * 455 * @param customPanel the custom panel 456 * @param defaultPanel the default panel 457 * @return the panel to use 458 */ 459 @Nullable 460 private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) { 461 if (customPanel == null) { 462 // Inflate the default panel, if needed. 463 if (defaultPanel instanceof ViewStub) { 464 defaultPanel = ((ViewStub) defaultPanel).inflate(); 465 } 466 467 return (ViewGroup) defaultPanel; 468 } 469 470 // Remove the default panel entirely. 471 if (defaultPanel != null) { 472 final ViewParent parent = defaultPanel.getParent(); 473 if (parent instanceof ViewGroup) { 474 ((ViewGroup) parent).removeView(defaultPanel); 475 } 476 } 477 478 // Inflate the custom panel, if needed. 479 if (customPanel instanceof ViewStub) { 480 customPanel = ((ViewStub) customPanel).inflate(); 481 } 482 483 return (ViewGroup) customPanel; 484 } 485 486 private void setupView() { 487 final View parentPanel = mWindow.findViewById(R.id.parentPanel); 488 final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel); 489 final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel); 490 final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel); 491 492 // Install custom content before setting up the title or buttons so 493 // that we can handle panel overrides. 494 final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel); 495 setupCustomContent(customPanel); 496 497 final View customTopPanel = customPanel.findViewById(R.id.topPanel); 498 final View customContentPanel = customPanel.findViewById(R.id.contentPanel); 499 final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel); 500 501 // Resolve the correct panels and remove the defaults, if needed. 502 final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel); 503 final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel); 504 final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel); 505 506 setupContent(contentPanel); 507 setupButtons(buttonPanel); 508 setupTitle(topPanel); 509 510 final boolean hasCustomPanel = customPanel != null 511 && customPanel.getVisibility() != View.GONE; 512 final boolean hasTopPanel = topPanel != null 513 && topPanel.getVisibility() != View.GONE; 514 final boolean hasButtonPanel = buttonPanel != null 515 && buttonPanel.getVisibility() != View.GONE; 516 517 // Only display the text spacer if we don't have buttons. 518 if (!hasButtonPanel) { 519 if (contentPanel != null) { 520 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons); 521 if (spacer != null) { 522 spacer.setVisibility(View.VISIBLE); 523 } 524 } 525 mWindow.setCloseOnTouchOutsideIfNotSet(true); 526 } 527 528 if (hasTopPanel) { 529 // Only clip scrolling content to padding if we have a title. 530 if (mScrollView != null) { 531 mScrollView.setClipToPadding(true); 532 } 533 534 // Only show the divider if we have a title. 535 View divider = null; 536 if (mMessage != null || mListView != null || hasCustomPanel) { 537 if (!hasCustomPanel) { 538 divider = topPanel.findViewById(R.id.titleDividerNoCustom); 539 } 540 if (divider == null) { 541 divider = topPanel.findViewById(R.id.titleDivider); 542 } 543 544 } else { 545 divider = topPanel.findViewById(R.id.titleDividerTop); 546 } 547 548 if (divider != null) { 549 divider.setVisibility(View.VISIBLE); 550 } 551 } else { 552 if (contentPanel != null) { 553 final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle); 554 if (spacer != null) { 555 spacer.setVisibility(View.VISIBLE); 556 } 557 } 558 } 559 560 if (mListView instanceof RecycleListView) { 561 ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel); 562 } 563 564 // Update scroll indicators as needed. 565 if (!hasCustomPanel) { 566 final View content = mListView != null ? mListView : mScrollView; 567 if (content != null) { 568 final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0) 569 | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0); 570 content.setScrollIndicators(indicators, 571 View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); 572 } 573 } 574 575 final TypedArray a = mContext.obtainStyledAttributes( 576 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); 577 setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, 578 hasTopPanel, hasCustomPanel, hasButtonPanel); 579 a.recycle(); 580 } 581 582 private void setupCustomContent(ViewGroup customPanel) { 583 final View customView; 584 if (mView != null) { 585 customView = mView; 586 } else if (mViewLayoutResId != 0) { 587 final LayoutInflater inflater = LayoutInflater.from(mContext); 588 customView = inflater.inflate(mViewLayoutResId, customPanel, false); 589 } else { 590 customView = null; 591 } 592 593 final boolean hasCustomView = customView != null; 594 if (!hasCustomView || !canTextInput(customView)) { 595 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 596 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 597 } 598 599 if (hasCustomView) { 600 final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); 601 custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 602 603 if (mViewSpacingSpecified) { 604 custom.setPadding( 605 mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); 606 } 607 608 if (mListView != null) { 609 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; 610 } 611 } else { 612 customPanel.setVisibility(View.GONE); 613 } 614 } 615 616 protected void setupTitle(ViewGroup topPanel) { 617 if (mCustomTitleView != null && mShowTitle) { 618 // Add the custom title view directly to the topPanel layout 619 final LayoutParams lp = new LayoutParams( 620 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 621 622 topPanel.addView(mCustomTitleView, 0, lp); 623 624 // Hide the title template 625 final View titleTemplate = mWindow.findViewById(R.id.title_template); 626 titleTemplate.setVisibility(View.GONE); 627 } else { 628 mIconView = (ImageView) mWindow.findViewById(R.id.icon); 629 630 final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); 631 if (hasTextTitle && mShowTitle) { 632 // Display the title if a title is supplied, else hide it. 633 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); 634 mTitleView.setText(mTitle); 635 636 // Do this last so that if the user has supplied any icons we 637 // use them instead of the default ones. If the user has 638 // specified 0 then make it disappear. 639 if (mIconId != 0) { 640 mIconView.setImageResource(mIconId); 641 } else if (mIcon != null) { 642 mIconView.setImageDrawable(mIcon); 643 } else { 644 // Apply the padding from the icon to ensure the title is 645 // aligned correctly. 646 mTitleView.setPadding(mIconView.getPaddingLeft(), 647 mIconView.getPaddingTop(), 648 mIconView.getPaddingRight(), 649 mIconView.getPaddingBottom()); 650 mIconView.setVisibility(View.GONE); 651 } 652 } else { 653 // Hide the title template 654 final View titleTemplate = mWindow.findViewById(R.id.title_template); 655 titleTemplate.setVisibility(View.GONE); 656 mIconView.setVisibility(View.GONE); 657 topPanel.setVisibility(View.GONE); 658 } 659 } 660 } 661 662 protected void setupContent(ViewGroup contentPanel) { 663 mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView); 664 mScrollView.setFocusable(false); 665 666 // Special case for users that only want to display a String 667 mMessageView = (TextView) contentPanel.findViewById(R.id.message); 668 if (mMessageView == null) { 669 return; 670 } 671 672 if (mMessage != null) { 673 mMessageView.setText(mMessage); 674 } else { 675 mMessageView.setVisibility(View.GONE); 676 mScrollView.removeView(mMessageView); 677 678 if (mListView != null) { 679 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent(); 680 final int childIndex = scrollParent.indexOfChild(mScrollView); 681 scrollParent.removeViewAt(childIndex); 682 scrollParent.addView(mListView, childIndex, 683 new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 684 } else { 685 contentPanel.setVisibility(View.GONE); 686 } 687 } 688 } 689 690 private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) { 691 if (upIndicator != null) { 692 upIndicator.setVisibility(v.canScrollVertically(-1) ? View.VISIBLE : View.INVISIBLE); 693 } 694 if (downIndicator != null) { 695 downIndicator.setVisibility(v.canScrollVertically(1) ? View.VISIBLE : View.INVISIBLE); 696 } 697 } 698 699 protected void setupButtons(ViewGroup buttonPanel) { 700 int BIT_BUTTON_POSITIVE = 1; 701 int BIT_BUTTON_NEGATIVE = 2; 702 int BIT_BUTTON_NEUTRAL = 4; 703 int whichButtons = 0; 704 mButtonPositive = (Button) buttonPanel.findViewById(R.id.button1); 705 mButtonPositive.setOnClickListener(mButtonHandler); 706 707 if (TextUtils.isEmpty(mButtonPositiveText)) { 708 mButtonPositive.setVisibility(View.GONE); 709 } else { 710 mButtonPositive.setText(mButtonPositiveText); 711 mButtonPositive.setVisibility(View.VISIBLE); 712 whichButtons = whichButtons | BIT_BUTTON_POSITIVE; 713 } 714 715 mButtonNegative = (Button) buttonPanel.findViewById(R.id.button2); 716 mButtonNegative.setOnClickListener(mButtonHandler); 717 718 if (TextUtils.isEmpty(mButtonNegativeText)) { 719 mButtonNegative.setVisibility(View.GONE); 720 } else { 721 mButtonNegative.setText(mButtonNegativeText); 722 mButtonNegative.setVisibility(View.VISIBLE); 723 724 whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; 725 } 726 727 mButtonNeutral = (Button) buttonPanel.findViewById(R.id.button3); 728 mButtonNeutral.setOnClickListener(mButtonHandler); 729 730 if (TextUtils.isEmpty(mButtonNeutralText)) { 731 mButtonNeutral.setVisibility(View.GONE); 732 } else { 733 mButtonNeutral.setText(mButtonNeutralText); 734 mButtonNeutral.setVisibility(View.VISIBLE); 735 736 whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; 737 } 738 739 if (shouldCenterSingleButton(mContext)) { 740 /* 741 * If we only have 1 button it should be centered on the layout and 742 * expand to fill 50% of the available space. 743 */ 744 if (whichButtons == BIT_BUTTON_POSITIVE) { 745 centerButton(mButtonPositive); 746 } else if (whichButtons == BIT_BUTTON_NEGATIVE) { 747 centerButton(mButtonNegative); 748 } else if (whichButtons == BIT_BUTTON_NEUTRAL) { 749 centerButton(mButtonNeutral); 750 } 751 } 752 753 final boolean hasButtons = whichButtons != 0; 754 if (!hasButtons) { 755 buttonPanel.setVisibility(View.GONE); 756 } 757 } 758 759 private void centerButton(Button button) { 760 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams(); 761 params.gravity = Gravity.CENTER_HORIZONTAL; 762 params.weight = 0.5f; 763 button.setLayoutParams(params); 764 View leftSpacer = mWindow.findViewById(R.id.leftSpacer); 765 if (leftSpacer != null) { 766 leftSpacer.setVisibility(View.VISIBLE); 767 } 768 View rightSpacer = mWindow.findViewById(R.id.rightSpacer); 769 if (rightSpacer != null) { 770 rightSpacer.setVisibility(View.VISIBLE); 771 } 772 } 773 774 private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel, 775 View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) { 776 int fullDark = 0; 777 int topDark = 0; 778 int centerDark = 0; 779 int bottomDark = 0; 780 int fullBright = 0; 781 int topBright = 0; 782 int centerBright = 0; 783 int bottomBright = 0; 784 int bottomMedium = 0; 785 786 // If the needsDefaultBackgrounds attribute is set, we know we're 787 // inheriting from a framework style. 788 final boolean needsDefaultBackgrounds = a.getBoolean( 789 R.styleable.AlertDialog_needsDefaultBackgrounds, true); 790 if (needsDefaultBackgrounds) { 791 fullDark = R.drawable.popup_full_dark; 792 topDark = R.drawable.popup_top_dark; 793 centerDark = R.drawable.popup_center_dark; 794 bottomDark = R.drawable.popup_bottom_dark; 795 fullBright = R.drawable.popup_full_bright; 796 topBright = R.drawable.popup_top_bright; 797 centerBright = R.drawable.popup_center_bright; 798 bottomBright = R.drawable.popup_bottom_bright; 799 bottomMedium = R.drawable.popup_bottom_medium; 800 } 801 802 topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright); 803 topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark); 804 centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright); 805 centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark); 806 807 /* We now set the background of all of the sections of the alert. 808 * First collect together each section that is being displayed along 809 * with whether it is on a light or dark background, then run through 810 * them setting their backgrounds. This is complicated because we need 811 * to correctly use the full, top, middle, and bottom graphics depending 812 * on how many views they are and where they appear. 813 */ 814 815 final View[] views = new View[4]; 816 final boolean[] light = new boolean[4]; 817 View lastView = null; 818 boolean lastLight = false; 819 820 int pos = 0; 821 if (hasTitle) { 822 views[pos] = topPanel; 823 light[pos] = false; 824 pos++; 825 } 826 827 /* The contentPanel displays either a custom text message or 828 * a ListView. If it's text we should use the dark background 829 * for ListView we should use the light background. If neither 830 * are there the contentPanel will be hidden so set it as null. 831 */ 832 views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel; 833 light[pos] = mListView != null; 834 pos++; 835 836 if (hasCustomView) { 837 views[pos] = customPanel; 838 light[pos] = mForceInverseBackground; 839 pos++; 840 } 841 842 if (hasButtons) { 843 views[pos] = buttonPanel; 844 light[pos] = true; 845 } 846 847 boolean setView = false; 848 for (pos = 0; pos < views.length; pos++) { 849 final View v = views[pos]; 850 if (v == null) { 851 continue; 852 } 853 854 if (lastView != null) { 855 if (!setView) { 856 lastView.setBackgroundResource(lastLight ? topBright : topDark); 857 } else { 858 lastView.setBackgroundResource(lastLight ? centerBright : centerDark); 859 } 860 setView = true; 861 } 862 863 lastView = v; 864 lastLight = light[pos]; 865 } 866 867 if (lastView != null) { 868 if (setView) { 869 bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright); 870 bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium); 871 bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark); 872 873 // ListViews will use the Bright background, but buttons use the 874 // Medium background. 875 lastView.setBackgroundResource( 876 lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark); 877 } else { 878 fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright); 879 fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark); 880 881 lastView.setBackgroundResource(lastLight ? fullBright : fullDark); 882 } 883 } 884 885 final ListView listView = mListView; 886 if (listView != null && mAdapter != null) { 887 listView.setAdapter(mAdapter); 888 final int checkedItem = mCheckedItem; 889 if (checkedItem > -1) { 890 listView.setItemChecked(checkedItem, true); 891 listView.setSelectionFromTop(checkedItem, 892 a.getDimensionPixelSize(R.styleable.AlertDialog_selectionScrollOffset, 0)); 893 } 894 } 895 } 896 897 public static class RecycleListView extends ListView { 898 private final int mPaddingTopNoTitle; 899 private final int mPaddingBottomNoButtons; 900 901 boolean mRecycleOnMeasure = true; 902 903 public RecycleListView(Context context) { 904 this(context, null); 905 } 906 907 public RecycleListView(Context context, AttributeSet attrs) { 908 super(context, attrs); 909 910 final TypedArray ta = context.obtainStyledAttributes( 911 attrs, R.styleable.RecycleListView); 912 mPaddingBottomNoButtons = ta.getDimensionPixelOffset( 913 R.styleable.RecycleListView_paddingBottomNoButtons, -1); 914 mPaddingTopNoTitle = ta.getDimensionPixelOffset( 915 R.styleable.RecycleListView_paddingTopNoTitle, -1); 916 } 917 918 public void setHasDecor(boolean hasTitle, boolean hasButtons) { 919 if (!hasButtons || !hasTitle) { 920 final int paddingLeft = getPaddingLeft(); 921 final int paddingTop = hasTitle ? getPaddingTop() : mPaddingTopNoTitle; 922 final int paddingRight = getPaddingRight(); 923 final int paddingBottom = hasButtons ? getPaddingBottom() : mPaddingBottomNoButtons; 924 setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); 925 } 926 } 927 928 @Override 929 protected boolean recycleOnMeasure() { 930 return mRecycleOnMeasure; 931 } 932 } 933 934 public static class AlertParams { 935 public final Context mContext; 936 public final LayoutInflater mInflater; 937 938 public int mIconId = 0; 939 public Drawable mIcon; 940 public int mIconAttrId = 0; 941 public CharSequence mTitle; 942 public View mCustomTitleView; 943 public CharSequence mMessage; 944 public CharSequence mPositiveButtonText; 945 public DialogInterface.OnClickListener mPositiveButtonListener; 946 public CharSequence mNegativeButtonText; 947 public DialogInterface.OnClickListener mNegativeButtonListener; 948 public CharSequence mNeutralButtonText; 949 public DialogInterface.OnClickListener mNeutralButtonListener; 950 public boolean mCancelable; 951 public DialogInterface.OnCancelListener mOnCancelListener; 952 public DialogInterface.OnDismissListener mOnDismissListener; 953 public DialogInterface.OnKeyListener mOnKeyListener; 954 public CharSequence[] mItems; 955 public ListAdapter mAdapter; 956 public DialogInterface.OnClickListener mOnClickListener; 957 public int mViewLayoutResId; 958 public View mView; 959 public int mViewSpacingLeft; 960 public int mViewSpacingTop; 961 public int mViewSpacingRight; 962 public int mViewSpacingBottom; 963 public boolean mViewSpacingSpecified = false; 964 public boolean[] mCheckedItems; 965 public boolean mIsMultiChoice; 966 public boolean mIsSingleChoice; 967 public int mCheckedItem = -1; 968 public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; 969 public Cursor mCursor; 970 public String mLabelColumn; 971 public String mIsCheckedColumn; 972 public boolean mForceInverseBackground; 973 public AdapterView.OnItemSelectedListener mOnItemSelectedListener; 974 public OnPrepareListViewListener mOnPrepareListViewListener; 975 public boolean mRecycleOnMeasure = true; 976 977 /** 978 * Interface definition for a callback to be invoked before the ListView 979 * will be bound to an adapter. 980 */ 981 public interface OnPrepareListViewListener { 982 983 /** 984 * Called before the ListView is bound to an adapter. 985 * @param listView The ListView that will be shown in the dialog. 986 */ 987 void onPrepareListView(ListView listView); 988 } 989 990 public AlertParams(Context context) { 991 mContext = context; 992 mCancelable = true; 993 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 994 } 995 996 public void apply(AlertController dialog) { 997 if (mCustomTitleView != null) { 998 dialog.setCustomTitle(mCustomTitleView); 999 } else { 1000 if (mTitle != null) { 1001 dialog.setTitle(mTitle); 1002 } 1003 if (mIcon != null) { 1004 dialog.setIcon(mIcon); 1005 } 1006 if (mIconId != 0) { 1007 dialog.setIcon(mIconId); 1008 } 1009 if (mIconAttrId != 0) { 1010 dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); 1011 } 1012 } 1013 if (mMessage != null) { 1014 dialog.setMessage(mMessage); 1015 } 1016 if (mPositiveButtonText != null) { 1017 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, 1018 mPositiveButtonListener, null); 1019 } 1020 if (mNegativeButtonText != null) { 1021 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, 1022 mNegativeButtonListener, null); 1023 } 1024 if (mNeutralButtonText != null) { 1025 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, 1026 mNeutralButtonListener, null); 1027 } 1028 if (mForceInverseBackground) { 1029 dialog.setInverseBackgroundForced(true); 1030 } 1031 // For a list, the client can either supply an array of items or an 1032 // adapter or a cursor 1033 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { 1034 createListView(dialog); 1035 } 1036 if (mView != null) { 1037 if (mViewSpacingSpecified) { 1038 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 1039 mViewSpacingBottom); 1040 } else { 1041 dialog.setView(mView); 1042 } 1043 } else if (mViewLayoutResId != 0) { 1044 dialog.setView(mViewLayoutResId); 1045 } 1046 1047 /* 1048 dialog.setCancelable(mCancelable); 1049 dialog.setOnCancelListener(mOnCancelListener); 1050 if (mOnKeyListener != null) { 1051 dialog.setOnKeyListener(mOnKeyListener); 1052 } 1053 */ 1054 } 1055 1056 private void createListView(final AlertController dialog) { 1057 final RecycleListView listView = 1058 (RecycleListView) mInflater.inflate(dialog.mListLayout, null); 1059 final ListAdapter adapter; 1060 1061 if (mIsMultiChoice) { 1062 if (mCursor == null) { 1063 adapter = new ArrayAdapter<CharSequence>( 1064 mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) { 1065 @Override 1066 public View getView(int position, View convertView, ViewGroup parent) { 1067 View view = super.getView(position, convertView, parent); 1068 if (mCheckedItems != null) { 1069 boolean isItemChecked = mCheckedItems[position]; 1070 if (isItemChecked) { 1071 listView.setItemChecked(position, true); 1072 } 1073 } 1074 return view; 1075 } 1076 }; 1077 } else { 1078 adapter = new CursorAdapter(mContext, mCursor, false) { 1079 private final int mLabelIndex; 1080 private final int mIsCheckedIndex; 1081 1082 { 1083 final Cursor cursor = getCursor(); 1084 mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn); 1085 mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn); 1086 } 1087 1088 @Override 1089 public void bindView(View view, Context context, Cursor cursor) { 1090 CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1); 1091 text.setText(cursor.getString(mLabelIndex)); 1092 listView.setItemChecked( 1093 cursor.getPosition(), 1094 cursor.getInt(mIsCheckedIndex) == 1); 1095 } 1096 1097 @Override 1098 public View newView(Context context, Cursor cursor, ViewGroup parent) { 1099 return mInflater.inflate(dialog.mMultiChoiceItemLayout, 1100 parent, false); 1101 } 1102 1103 }; 1104 } 1105 } else { 1106 final int layout; 1107 if (mIsSingleChoice) { 1108 layout = dialog.mSingleChoiceItemLayout; 1109 } else { 1110 layout = dialog.mListItemLayout; 1111 } 1112 1113 if (mCursor != null) { 1114 adapter = new SimpleCursorAdapter(mContext, layout, mCursor, 1115 new String[] { mLabelColumn }, new int[] { R.id.text1 }); 1116 } else if (mAdapter != null) { 1117 adapter = mAdapter; 1118 } else { 1119 adapter = new CheckedItemAdapter(mContext, layout, R.id.text1, mItems); 1120 } 1121 } 1122 1123 if (mOnPrepareListViewListener != null) { 1124 mOnPrepareListViewListener.onPrepareListView(listView); 1125 } 1126 1127 /* Don't directly set the adapter on the ListView as we might 1128 * want to add a footer to the ListView later. 1129 */ 1130 dialog.mAdapter = adapter; 1131 dialog.mCheckedItem = mCheckedItem; 1132 1133 if (mOnClickListener != null) { 1134 listView.setOnItemClickListener(new OnItemClickListener() { 1135 @Override 1136 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 1137 mOnClickListener.onClick(dialog.mDialogInterface, position); 1138 if (!mIsSingleChoice) { 1139 dialog.mDialogInterface.dismiss(); 1140 } 1141 } 1142 }); 1143 } else if (mOnCheckboxClickListener != null) { 1144 listView.setOnItemClickListener(new OnItemClickListener() { 1145 @Override 1146 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 1147 if (mCheckedItems != null) { 1148 mCheckedItems[position] = listView.isItemChecked(position); 1149 } 1150 mOnCheckboxClickListener.onClick( 1151 dialog.mDialogInterface, position, listView.isItemChecked(position)); 1152 } 1153 }); 1154 } 1155 1156 // Attach a given OnItemSelectedListener to the ListView 1157 if (mOnItemSelectedListener != null) { 1158 listView.setOnItemSelectedListener(mOnItemSelectedListener); 1159 } 1160 1161 if (mIsSingleChoice) { 1162 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 1163 } else if (mIsMultiChoice) { 1164 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 1165 } 1166 listView.mRecycleOnMeasure = mRecycleOnMeasure; 1167 dialog.mListView = listView; 1168 } 1169 } 1170 1171 private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { 1172 public CheckedItemAdapter(Context context, int resource, int textViewResourceId, 1173 CharSequence[] objects) { 1174 super(context, resource, textViewResourceId, objects); 1175 } 1176 1177 @Override 1178 public boolean hasStableIds() { 1179 return true; 1180 } 1181 1182 @Override 1183 public long getItemId(int position) { 1184 return position; 1185 } 1186 } 1187} 1188