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