• 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.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