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