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