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