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