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