AlertController.java revision 076c7fbeda5c4205e3e52fbc6110ab1c4b342afe
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
17package com.android.internal.app;
18
19import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20
21import com.android.internal.R;
22
23import android.app.AlertDialog;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.res.TypedArray;
27import android.database.Cursor;
28import android.graphics.drawable.Drawable;
29import android.os.Handler;
30import android.os.Message;
31import android.text.TextUtils;
32import android.util.AttributeSet;
33import android.view.Gravity;
34import android.view.KeyEvent;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.ViewGroup.LayoutParams;
39import android.view.Window;
40import android.view.WindowManager;
41import android.widget.AdapterView;
42import android.widget.AdapterView.OnItemClickListener;
43import android.widget.ArrayAdapter;
44import android.widget.Button;
45import android.widget.CheckedTextView;
46import android.widget.CursorAdapter;
47import android.widget.FrameLayout;
48import android.widget.ImageView;
49import android.widget.LinearLayout;
50import android.widget.ListAdapter;
51import android.widget.ListView;
52import android.widget.ScrollView;
53import android.widget.SimpleCursorAdapter;
54import android.widget.TextView;
55
56import java.lang.ref.WeakReference;
57
58public class AlertController {
59
60    private final Context mContext;
61    private final DialogInterface mDialogInterface;
62    private final Window mWindow;
63
64    private CharSequence mTitle;
65
66    private CharSequence mMessage;
67
68    private ListView mListView;
69
70    private View mView;
71
72    private int mViewSpacingLeft;
73
74    private int mViewSpacingTop;
75
76    private int mViewSpacingRight;
77
78    private int mViewSpacingBottom;
79
80    private boolean mViewSpacingSpecified = false;
81
82    private Button mButtonPositive;
83
84    private CharSequence mButtonPositiveText;
85
86    private Message mButtonPositiveMessage;
87
88    private Button mButtonNegative;
89
90    private CharSequence mButtonNegativeText;
91
92    private Message mButtonNegativeMessage;
93
94    private Button mButtonNeutral;
95
96    private CharSequence mButtonNeutralText;
97
98    private Message mButtonNeutralMessage;
99
100    private ScrollView mScrollView;
101
102    private int mIconId = -1;
103
104    private Drawable mIcon;
105
106    private ImageView mIconView;
107
108    private TextView mTitleView;
109
110    private TextView mMessageView;
111
112    private View mCustomTitleView;
113
114    private boolean mForceInverseBackground;
115
116    private ListAdapter mAdapter;
117
118    private int mCheckedItem = -1;
119
120    private Handler mHandler;
121
122    View.OnClickListener mButtonHandler = new View.OnClickListener() {
123        public void onClick(View v) {
124            Message m = null;
125            if (v == mButtonPositive && mButtonPositiveMessage != null) {
126                m = Message.obtain(mButtonPositiveMessage);
127            } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
128                m = Message.obtain(mButtonNegativeMessage);
129            } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
130                m = Message.obtain(mButtonNeutralMessage);
131            }
132            if (m != null) {
133                m.sendToTarget();
134            }
135
136            // Post a message so we dismiss after the above handlers are executed
137            mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
138                    .sendToTarget();
139        }
140    };
141
142    private static final class ButtonHandler extends Handler {
143        // Button clicks have Message.what as the BUTTON{1,2,3} constant
144        private static final int MSG_DISMISS_DIALOG = 1;
145
146        private WeakReference<DialogInterface> mDialog;
147
148        public ButtonHandler(DialogInterface dialog) {
149            mDialog = new WeakReference<DialogInterface>(dialog);
150        }
151
152        @Override
153        public void handleMessage(Message msg) {
154            switch (msg.what) {
155
156                case DialogInterface.BUTTON_POSITIVE:
157                case DialogInterface.BUTTON_NEGATIVE:
158                case DialogInterface.BUTTON_NEUTRAL:
159                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
160                    break;
161
162                case MSG_DISMISS_DIALOG:
163                    ((DialogInterface) msg.obj).dismiss();
164            }
165        }
166    }
167
168    public AlertController(Context context, DialogInterface di, Window window) {
169        mContext = context;
170        mDialogInterface = di;
171        mWindow = window;
172        mHandler = new ButtonHandler(di);
173    }
174
175    static boolean canTextInput(View v) {
176        if (v.onCheckIsTextEditor()) {
177            return true;
178        }
179
180        if (!(v instanceof ViewGroup)) {
181            return false;
182        }
183
184        ViewGroup vg = (ViewGroup)v;
185        int i = vg.getChildCount();
186        while (i > 0) {
187            i--;
188            v = vg.getChildAt(i);
189            if (canTextInput(v)) {
190                return true;
191            }
192        }
193
194        return false;
195    }
196
197    public void installContent() {
198        /* We use a custom title so never request a window title */
199        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
200
201        if (mView == null || !canTextInput(mView)) {
202            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
203                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
204        }
205        mWindow.setContentView(com.android.internal.R.layout.alert_dialog);
206        setupView();
207    }
208
209    public void setTitle(CharSequence title) {
210        mTitle = title;
211        if (mTitleView != null) {
212            mTitleView.setText(title);
213        }
214    }
215
216    /**
217     * @see AlertDialog.Builder#setCustomTitle(View)
218     */
219    public void setCustomTitle(View customTitleView) {
220        mCustomTitleView = customTitleView;
221    }
222
223    public void setMessage(CharSequence message) {
224        mMessage = message;
225        if (mMessageView != null) {
226            mMessageView.setText(message);
227        }
228    }
229
230    /**
231     * Set the view to display in the dialog.
232     */
233    public void setView(View view) {
234        mView = view;
235        mViewSpacingSpecified = false;
236    }
237
238    /**
239     * Set the view to display in the dialog along with the spacing around that view
240     */
241    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
242            int viewSpacingBottom) {
243        mView = view;
244        mViewSpacingSpecified = true;
245        mViewSpacingLeft = viewSpacingLeft;
246        mViewSpacingTop = viewSpacingTop;
247        mViewSpacingRight = viewSpacingRight;
248        mViewSpacingBottom = viewSpacingBottom;
249    }
250
251    /**
252     * Sets a click listener or a message to be sent when the button is clicked.
253     * You only need to pass one of {@code listener} or {@code msg}.
254     *
255     * @param whichButton Which button, can be one of
256     *            {@link DialogInterface#BUTTON_POSITIVE},
257     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
258     *            {@link DialogInterface#BUTTON_NEUTRAL}
259     * @param text The text to display in positive button.
260     * @param listener The {@link DialogInterface.OnClickListener} to use.
261     * @param msg The {@link Message} to be sent when clicked.
262     */
263    public void setButton(int whichButton, CharSequence text,
264            DialogInterface.OnClickListener listener, Message msg) {
265
266        if (msg == null && listener != null) {
267            msg = mHandler.obtainMessage(whichButton, listener);
268        }
269
270        switch (whichButton) {
271
272            case DialogInterface.BUTTON_POSITIVE:
273                mButtonPositiveText = text;
274                mButtonPositiveMessage = msg;
275                break;
276
277            case DialogInterface.BUTTON_NEGATIVE:
278                mButtonNegativeText = text;
279                mButtonNegativeMessage = msg;
280                break;
281
282            case DialogInterface.BUTTON_NEUTRAL:
283                mButtonNeutralText = text;
284                mButtonNeutralMessage = msg;
285                break;
286
287            default:
288                throw new IllegalArgumentException("Button does not exist");
289        }
290    }
291
292    /**
293     * Set resId to 0 if you don't want an icon.
294     * @param resId the resourceId of the drawable to use as the icon or 0
295     * if you don't want an icon.
296     */
297    public void setIcon(int resId) {
298        mIconId = resId;
299        if (mIconView != null) {
300            if (resId > 0) {
301                mIconView.setImageResource(mIconId);
302            } else if (resId == 0) {
303                mIconView.setVisibility(View.GONE);
304            }
305        }
306    }
307
308    public void setIcon(Drawable icon) {
309        mIcon = icon;
310        if ((mIconView != null) && (mIcon != null)) {
311            mIconView.setImageDrawable(icon);
312        }
313    }
314
315    public void setInverseBackgroundForced(boolean forceInverseBackground) {
316        mForceInverseBackground = forceInverseBackground;
317    }
318
319    public ListView getListView() {
320        return mListView;
321    }
322
323    public Button getButton(int whichButton) {
324        switch (whichButton) {
325            case DialogInterface.BUTTON_POSITIVE:
326                return mButtonPositive;
327            case DialogInterface.BUTTON_NEGATIVE:
328                return mButtonNegative;
329            case DialogInterface.BUTTON_NEUTRAL:
330                return mButtonNeutral;
331            default:
332                return null;
333        }
334    }
335
336    @SuppressWarnings({"UnusedDeclaration"})
337    public boolean onKeyDown(int keyCode, KeyEvent event) {
338        return mScrollView != null && mScrollView.executeKeyEvent(event);
339    }
340
341    @SuppressWarnings({"UnusedDeclaration"})
342    public boolean onKeyUp(int keyCode, KeyEvent event) {
343        return mScrollView != null && mScrollView.executeKeyEvent(event);
344    }
345
346    private void setupView() {
347        LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
348        setupContent(contentPanel);
349        boolean hasButtons = setupButtons();
350
351        LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
352        TypedArray a = mContext.obtainStyledAttributes(
353                null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
354        boolean hasTitle = setupTitle(topPanel);
355
356        View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
357        if (!hasButtons) {
358            buttonPanel.setVisibility(View.GONE);
359        }
360
361        FrameLayout customPanel = null;
362        if (mView != null) {
363            customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
364            FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
365            custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
366            if (mViewSpacingSpecified) {
367                custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
368                        mViewSpacingBottom);
369            }
370            if (mListView != null) {
371                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
372            }
373        } else {
374            mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
375        }
376
377        /* Only display the divider if we have a title and a
378         * custom view or a message.
379         */
380        if (hasTitle && ((mMessage != null) || (mView != null))) {
381            View divider = mWindow.findViewById(R.id.titleDivider);
382            divider.setVisibility(View.VISIBLE);
383        }
384
385        setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
386        a.recycle();
387    }
388
389    private boolean setupTitle(LinearLayout topPanel) {
390        boolean hasTitle = true;
391
392        if (mCustomTitleView != null) {
393            // Add the custom title view directly to the topPanel layout
394            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
395                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
396
397            topPanel.addView(mCustomTitleView, lp);
398
399            // Hide the title template
400            View titleTemplate = mWindow.findViewById(R.id.title_template);
401            titleTemplate.setVisibility(View.GONE);
402        } else {
403            final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
404
405            mIconView = (ImageView) mWindow.findViewById(R.id.icon);
406            if (hasTextTitle) {
407                /* Display the title if a title is supplied, else hide it */
408                mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
409
410                mTitleView.setText(mTitle);
411
412                /* Do this last so that if the user has supplied any
413                 * icons we use them instead of the default ones. If the
414                 * user has specified 0 then make it disappear.
415                 */
416                if (mIconId > 0) {
417                    mIconView.setImageResource(mIconId);
418                } else if (mIcon != null) {
419                    mIconView.setImageDrawable(mIcon);
420                } else if (mIconId == 0) {
421
422                    /* Apply the padding from the icon to ensure the
423                     * title is aligned correctly.
424                     */
425                    mTitleView.setPadding(mIconView.getPaddingLeft(),
426                            mIconView.getPaddingTop(),
427                            mIconView.getPaddingRight(),
428                            mIconView.getPaddingBottom());
429                    mIconView.setVisibility(View.GONE);
430                }
431            } else {
432
433                // Hide the title template
434                View titleTemplate = mWindow.findViewById(R.id.title_template);
435                titleTemplate.setVisibility(View.GONE);
436                mIconView.setVisibility(View.GONE);
437                hasTitle = false;
438            }
439        }
440        return hasTitle;
441    }
442
443    private void setupContent(LinearLayout contentPanel) {
444        mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
445        mScrollView.setFocusable(false);
446
447        // Special case for users that only want to display a String
448        mMessageView = (TextView) mWindow.findViewById(R.id.message);
449        if (mMessageView == null) {
450            return;
451        }
452
453        if (mMessage != null) {
454            mMessageView.setText(mMessage);
455        } else {
456            mMessageView.setVisibility(View.GONE);
457            mScrollView.removeView(mMessageView);
458
459            if (mListView != null) {
460                contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
461                contentPanel.addView(mListView,
462                        new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
463                contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
464            } else {
465                contentPanel.setVisibility(View.GONE);
466            }
467        }
468    }
469
470    private boolean setupButtons() {
471        int BIT_BUTTON_POSITIVE = 1;
472        int BIT_BUTTON_NEGATIVE = 2;
473        int BIT_BUTTON_NEUTRAL = 4;
474        int whichButtons = 0;
475        mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
476        mButtonPositive.setOnClickListener(mButtonHandler);
477
478        if (TextUtils.isEmpty(mButtonPositiveText)) {
479            mButtonPositive.setVisibility(View.GONE);
480        } else {
481            mButtonPositive.setText(mButtonPositiveText);
482            mButtonPositive.setVisibility(View.VISIBLE);
483            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
484        }
485
486        mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
487        mButtonNegative.setOnClickListener(mButtonHandler);
488
489        if (TextUtils.isEmpty(mButtonNegativeText)) {
490            mButtonNegative.setVisibility(View.GONE);
491        } else {
492            mButtonNegative.setText(mButtonNegativeText);
493            mButtonNegative.setVisibility(View.VISIBLE);
494
495            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
496        }
497
498        mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
499        mButtonNeutral.setOnClickListener(mButtonHandler);
500
501        if (TextUtils.isEmpty(mButtonNeutralText)) {
502            mButtonNeutral.setVisibility(View.GONE);
503        } else {
504            mButtonNeutral.setText(mButtonNeutralText);
505            mButtonNeutral.setVisibility(View.VISIBLE);
506
507            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
508        }
509
510        /*
511         * If we only have 1 button it should be centered on the layout and
512         * expand to fill 50% of the available space.
513         */
514        if (whichButtons == BIT_BUTTON_POSITIVE) {
515            centerButton(mButtonPositive);
516        } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
517            centerButton(mButtonNeutral);
518        } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
519            centerButton(mButtonNeutral);
520        }
521
522        return whichButtons != 0;
523    }
524
525    private void centerButton(Button button) {
526        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
527        params.gravity = Gravity.CENTER_HORIZONTAL;
528        params.weight = 0.5f;
529        button.setLayoutParams(params);
530        View leftSpacer = mWindow.findViewById(R.id.leftSpacer);
531        leftSpacer.setVisibility(View.VISIBLE);
532        View rightSpacer = mWindow.findViewById(R.id.rightSpacer);
533        rightSpacer.setVisibility(View.VISIBLE);
534    }
535
536    private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
537            View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle,
538            View buttonPanel) {
539
540        /* Get all the different background required */
541        int fullDark = a.getResourceId(
542                R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
543        int topDark = a.getResourceId(
544                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
545        int centerDark = a.getResourceId(
546                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
547        int bottomDark = a.getResourceId(
548                R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
549        int fullBright = a.getResourceId(
550                R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
551        int topBright = a.getResourceId(
552                R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
553        int centerBright = a.getResourceId(
554                R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
555        int bottomBright = a.getResourceId(
556                R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
557        int bottomMedium = a.getResourceId(
558                R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
559
560        /*
561         * We now set the background of all of the sections of the alert.
562         * First collect together each section that is being displayed along
563         * with whether it is on a light or dark background, then run through
564         * them setting their backgrounds.  This is complicated because we need
565         * to correctly use the full, top, middle, and bottom graphics depending
566         * on how many views they are and where they appear.
567         */
568
569        View[] views = new View[4];
570        boolean[] light = new boolean[4];
571        View lastView = null;
572        boolean lastLight = false;
573
574        int pos = 0;
575        if (hasTitle) {
576            views[pos] = topPanel;
577            light[pos] = false;
578            pos++;
579        }
580
581        /* The contentPanel displays either a custom text message or
582         * a ListView. If it's text we should use the dark background
583         * for ListView we should use the light background. If neither
584         * are there the contentPanel will be hidden so set it as null.
585         */
586        views[pos] = (contentPanel.getVisibility() == View.GONE)
587                ? null : contentPanel;
588        light[pos] = mListView != null;
589        pos++;
590        if (customPanel != null) {
591            views[pos] = customPanel;
592            light[pos] = mForceInverseBackground;
593            pos++;
594        }
595        if (hasButtons) {
596            views[pos] = buttonPanel;
597            light[pos] = true;
598        }
599
600        boolean setView = false;
601        for (pos=0; pos<views.length; pos++) {
602            View v = views[pos];
603            if (v == null) {
604                continue;
605            }
606            if (lastView != null) {
607                if (!setView) {
608                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
609                } else {
610                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
611                }
612                setView = true;
613            }
614            lastView = v;
615            lastLight = light[pos];
616        }
617
618        if (lastView != null) {
619            if (setView) {
620
621                /* ListViews will use the Bright background but buttons use
622                 * the Medium background.
623                 */
624                lastView.setBackgroundResource(
625                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
626            } else {
627                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
628            }
629        }
630
631        /* TODO: uncomment section below. The logic for this should be if
632         * it's a Contextual menu being displayed AND only a Cancel button
633         * is shown then do this.
634         */
635//        if (hasButtons && (mListView != null)) {
636
637            /* Yet another *special* case. If there is a ListView with buttons
638             * don't put the buttons on the bottom but instead put them in the
639             * footer of the ListView this will allow more items to be
640             * displayed.
641             */
642
643            /*
644            contentPanel.setBackgroundResource(bottomBright);
645            buttonPanel.setBackgroundResource(centerMedium);
646            ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
647            parent.removeView(buttonPanel);
648            AbsListView.LayoutParams params = new AbsListView.LayoutParams(
649                    AbsListView.LayoutParams.MATCH_PARENT,
650                    AbsListView.LayoutParams.MATCH_PARENT);
651            buttonPanel.setLayoutParams(params);
652            mListView.addFooterView(buttonPanel);
653            */
654//        }
655
656        if ((mListView != null) && (mAdapter != null)) {
657            mListView.setAdapter(mAdapter);
658            if (mCheckedItem > -1) {
659                mListView.setItemChecked(mCheckedItem, true);
660                mListView.setSelection(mCheckedItem);
661            }
662        }
663    }
664
665    public static class RecycleListView extends ListView {
666        boolean mRecycleOnMeasure = true;
667
668        public RecycleListView(Context context) {
669            super(context);
670        }
671
672        public RecycleListView(Context context, AttributeSet attrs) {
673            super(context, attrs);
674        }
675
676        public RecycleListView(Context context, AttributeSet attrs, int defStyle) {
677            super(context, attrs, defStyle);
678        }
679
680        @Override
681        protected boolean recycleOnMeasure() {
682            return mRecycleOnMeasure;
683        }
684    }
685
686    public static class AlertParams {
687        public final Context mContext;
688        public final LayoutInflater mInflater;
689
690        public int mIconId = 0;
691        public Drawable mIcon;
692        public CharSequence mTitle;
693        public View mCustomTitleView;
694        public CharSequence mMessage;
695        public CharSequence mPositiveButtonText;
696        public DialogInterface.OnClickListener mPositiveButtonListener;
697        public CharSequence mNegativeButtonText;
698        public DialogInterface.OnClickListener mNegativeButtonListener;
699        public CharSequence mNeutralButtonText;
700        public DialogInterface.OnClickListener mNeutralButtonListener;
701        public boolean mCancelable;
702        public DialogInterface.OnCancelListener mOnCancelListener;
703        public DialogInterface.OnKeyListener mOnKeyListener;
704        public CharSequence[] mItems;
705        public ListAdapter mAdapter;
706        public DialogInterface.OnClickListener mOnClickListener;
707        public View mView;
708        public int mViewSpacingLeft;
709        public int mViewSpacingTop;
710        public int mViewSpacingRight;
711        public int mViewSpacingBottom;
712        public boolean mViewSpacingSpecified = false;
713        public boolean[] mCheckedItems;
714        public boolean mIsMultiChoice;
715        public boolean mIsSingleChoice;
716        public int mCheckedItem = -1;
717        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
718        public Cursor mCursor;
719        public String mLabelColumn;
720        public String mIsCheckedColumn;
721        public boolean mForceInverseBackground;
722        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
723        public OnPrepareListViewListener mOnPrepareListViewListener;
724        public boolean mRecycleOnMeasure = true;
725
726        /**
727         * Interface definition for a callback to be invoked before the ListView
728         * will be bound to an adapter.
729         */
730        public interface OnPrepareListViewListener {
731
732            /**
733             * Called before the ListView is bound to an adapter.
734             * @param listView The ListView that will be shown in the dialog.
735             */
736            void onPrepareListView(ListView listView);
737        }
738
739        public AlertParams(Context context) {
740            mContext = context;
741            mCancelable = true;
742            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
743        }
744
745        public void apply(AlertController dialog) {
746            if (mCustomTitleView != null) {
747                dialog.setCustomTitle(mCustomTitleView);
748            } else {
749                if (mTitle != null) {
750                    dialog.setTitle(mTitle);
751                }
752                if (mIcon != null) {
753                    dialog.setIcon(mIcon);
754                }
755                if (mIconId >= 0) {
756                    dialog.setIcon(mIconId);
757                }
758            }
759            if (mMessage != null) {
760                dialog.setMessage(mMessage);
761            }
762            if (mPositiveButtonText != null) {
763                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
764                        mPositiveButtonListener, null);
765            }
766            if (mNegativeButtonText != null) {
767                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
768                        mNegativeButtonListener, null);
769            }
770            if (mNeutralButtonText != null) {
771                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
772                        mNeutralButtonListener, null);
773            }
774            if (mForceInverseBackground) {
775                dialog.setInverseBackgroundForced(true);
776            }
777            // For a list, the client can either supply an array of items or an
778            // adapter or a cursor
779            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
780                createListView(dialog);
781            }
782            if (mView != null) {
783                if (mViewSpacingSpecified) {
784                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
785                            mViewSpacingBottom);
786                } else {
787                    dialog.setView(mView);
788                }
789            }
790
791            /*
792            dialog.setCancelable(mCancelable);
793            dialog.setOnCancelListener(mOnCancelListener);
794            if (mOnKeyListener != null) {
795                dialog.setOnKeyListener(mOnKeyListener);
796            }
797            */
798        }
799
800        private void createListView(final AlertController dialog) {
801            final RecycleListView listView = (RecycleListView)
802                    mInflater.inflate(R.layout.select_dialog, null);
803            ListAdapter adapter;
804
805            if (mIsMultiChoice) {
806                if (mCursor == null) {
807                    adapter = new ArrayAdapter<CharSequence>(
808                            mContext, R.layout.select_dialog_multichoice, R.id.text1, mItems) {
809                        @Override
810                        public View getView(int position, View convertView, ViewGroup parent) {
811                            View view = super.getView(position, convertView, parent);
812                            if (mCheckedItems != null) {
813                                boolean isItemChecked = mCheckedItems[position];
814                                if (isItemChecked) {
815                                    listView.setItemChecked(position, true);
816                                }
817                            }
818                            return view;
819                        }
820                    };
821                } else {
822                    adapter = new CursorAdapter(mContext, mCursor, false) {
823                        private final int mLabelIndex;
824                        private final int mIsCheckedIndex;
825
826                        {
827                            final Cursor cursor = getCursor();
828                            mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
829                            mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
830                        }
831
832                        @Override
833                        public void bindView(View view, Context context, Cursor cursor) {
834                            CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
835                            text.setText(cursor.getString(mLabelIndex));
836                            listView.setItemChecked(cursor.getPosition(),
837                                    cursor.getInt(mIsCheckedIndex) == 1);
838                        }
839
840                        @Override
841                        public View newView(Context context, Cursor cursor, ViewGroup parent) {
842                            return mInflater.inflate(R.layout.select_dialog_multichoice,
843                                    parent, false);
844                        }
845
846                    };
847                }
848            } else {
849                int layout = mIsSingleChoice
850                        ? R.layout.select_dialog_singlechoice : R.layout.select_dialog_item;
851                if (mCursor == null) {
852                    adapter = (mAdapter != null) ? mAdapter
853                            : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems);
854                } else {
855                    adapter = new SimpleCursorAdapter(mContext, layout,
856                            mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1});
857                }
858            }
859
860            if (mOnPrepareListViewListener != null) {
861                mOnPrepareListViewListener.onPrepareListView(listView);
862            }
863
864            /* Don't directly set the adapter on the ListView as we might
865             * want to add a footer to the ListView later.
866             */
867            dialog.mAdapter = adapter;
868            dialog.mCheckedItem = mCheckedItem;
869
870            if (mOnClickListener != null) {
871                listView.setOnItemClickListener(new OnItemClickListener() {
872                    public void onItemClick(AdapterView parent, View v, int position, long id) {
873                        mOnClickListener.onClick(dialog.mDialogInterface, position);
874                        if (!mIsSingleChoice) {
875                            dialog.mDialogInterface.dismiss();
876                        }
877                    }
878                });
879            } else if (mOnCheckboxClickListener != null) {
880                listView.setOnItemClickListener(new OnItemClickListener() {
881                    public void onItemClick(AdapterView parent, View v, int position, long id) {
882                        if (mCheckedItems != null) {
883                            mCheckedItems[position] = listView.isItemChecked(position);
884                        }
885                        mOnCheckboxClickListener.onClick(
886                                dialog.mDialogInterface, position, listView.isItemChecked(position));
887                    }
888                });
889            }
890
891            // Attach a given OnItemSelectedListener to the ListView
892            if (mOnItemSelectedListener != null) {
893                listView.setOnItemSelectedListener(mOnItemSelectedListener);
894            }
895
896            if (mIsSingleChoice) {
897                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
898            } else if (mIsMultiChoice) {
899                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
900            }
901            listView.mRecycleOnMeasure = mRecycleOnMeasure;
902            dialog.mListView = listView;
903        }
904    }
905
906}
907