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    /**
351     * @param attrId the attributeId of the theme-specific drawable
352     * to resolve the resourceId for.
353     *
354     * @return resId the resourceId of the theme-specific drawable
355     */
356    public int getIconAttributeResId(int attrId) {
357        TypedValue out = new TypedValue();
358        mContext.getTheme().resolveAttribute(attrId, out, true);
359        return out.resourceId;
360    }
361
362    public void setInverseBackgroundForced(boolean forceInverseBackground) {
363        mForceInverseBackground = forceInverseBackground;
364    }
365
366    public ListView getListView() {
367        return mListView;
368    }
369
370    public Button getButton(int whichButton) {
371        switch (whichButton) {
372            case DialogInterface.BUTTON_POSITIVE:
373                return mButtonPositive;
374            case DialogInterface.BUTTON_NEGATIVE:
375                return mButtonNegative;
376            case DialogInterface.BUTTON_NEUTRAL:
377                return mButtonNeutral;
378            default:
379                return null;
380        }
381    }
382
383    @SuppressWarnings({"UnusedDeclaration"})
384    public boolean onKeyDown(int keyCode, KeyEvent event) {
385        return mScrollView != null && mScrollView.executeKeyEvent(event);
386    }
387
388    @SuppressWarnings({"UnusedDeclaration"})
389    public boolean onKeyUp(int keyCode, KeyEvent event) {
390        return mScrollView != null && mScrollView.executeKeyEvent(event);
391    }
392
393    private void setupView() {
394        LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
395        setupContent(contentPanel);
396        boolean hasButtons = setupButtons();
397
398        LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
399        TypedArray a = mContext.obtainStyledAttributes(
400                null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
401        boolean hasTitle = setupTitle(topPanel);
402
403        View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
404        if (!hasButtons) {
405            buttonPanel.setVisibility(View.GONE);
406            mWindow.setCloseOnTouchOutsideIfNotSet(true);
407        }
408
409        FrameLayout customPanel = null;
410        if (mView != null) {
411            customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
412            FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
413            custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
414            if (mViewSpacingSpecified) {
415                custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
416                        mViewSpacingBottom);
417            }
418            if (mListView != null) {
419                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
420            }
421        } else {
422            mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
423        }
424
425        /* Only display the divider if we have a title and a
426         * custom view or a message.
427         */
428        if (hasTitle) {
429            View divider = null;
430            if (mMessage != null || mView != null || mListView != null) {
431                divider = mWindow.findViewById(R.id.titleDivider);
432            } else {
433                divider = mWindow.findViewById(R.id.titleDividerTop);
434            }
435
436            if (divider != null) {
437                divider.setVisibility(View.VISIBLE);
438            }
439        }
440
441        setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
442        a.recycle();
443    }
444
445    private boolean setupTitle(LinearLayout topPanel) {
446        boolean hasTitle = true;
447
448        if (mCustomTitleView != null) {
449            // Add the custom title view directly to the topPanel layout
450            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
451                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
452
453            topPanel.addView(mCustomTitleView, 0, lp);
454
455            // Hide the title template
456            View titleTemplate = mWindow.findViewById(R.id.title_template);
457            titleTemplate.setVisibility(View.GONE);
458        } else {
459            final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
460
461            mIconView = (ImageView) mWindow.findViewById(R.id.icon);
462            if (hasTextTitle) {
463                /* Display the title if a title is supplied, else hide it */
464                mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
465
466                mTitleView.setText(mTitle);
467
468                /* Do this last so that if the user has supplied any
469                 * icons we use them instead of the default ones. If the
470                 * user has specified 0 then make it disappear.
471                 */
472                if (mIconId > 0) {
473                    mIconView.setImageResource(mIconId);
474                } else if (mIcon != null) {
475                    mIconView.setImageDrawable(mIcon);
476                } else if (mIconId == 0) {
477
478                    /* Apply the padding from the icon to ensure the
479                     * title is aligned correctly.
480                     */
481                    mTitleView.setPadding(mIconView.getPaddingLeft(),
482                            mIconView.getPaddingTop(),
483                            mIconView.getPaddingRight(),
484                            mIconView.getPaddingBottom());
485                    mIconView.setVisibility(View.GONE);
486                }
487            } else {
488
489                // Hide the title template
490                View titleTemplate = mWindow.findViewById(R.id.title_template);
491                titleTemplate.setVisibility(View.GONE);
492                mIconView.setVisibility(View.GONE);
493                topPanel.setVisibility(View.GONE);
494                hasTitle = false;
495            }
496        }
497        return hasTitle;
498    }
499
500    private void setupContent(LinearLayout contentPanel) {
501        mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
502        mScrollView.setFocusable(false);
503
504        // Special case for users that only want to display a String
505        mMessageView = (TextView) mWindow.findViewById(R.id.message);
506        if (mMessageView == null) {
507            return;
508        }
509
510        if (mMessage != null) {
511            mMessageView.setText(mMessage);
512        } else {
513            mMessageView.setVisibility(View.GONE);
514            mScrollView.removeView(mMessageView);
515
516            if (mListView != null) {
517                contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
518                contentPanel.addView(mListView,
519                        new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
520                contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
521            } else {
522                contentPanel.setVisibility(View.GONE);
523            }
524        }
525    }
526
527    private boolean setupButtons() {
528        int BIT_BUTTON_POSITIVE = 1;
529        int BIT_BUTTON_NEGATIVE = 2;
530        int BIT_BUTTON_NEUTRAL = 4;
531        int whichButtons = 0;
532        mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
533        mButtonPositive.setOnClickListener(mButtonHandler);
534
535        if (TextUtils.isEmpty(mButtonPositiveText)) {
536            mButtonPositive.setVisibility(View.GONE);
537        } else {
538            mButtonPositive.setText(mButtonPositiveText);
539            mButtonPositive.setVisibility(View.VISIBLE);
540            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
541        }
542
543        mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
544        mButtonNegative.setOnClickListener(mButtonHandler);
545
546        if (TextUtils.isEmpty(mButtonNegativeText)) {
547            mButtonNegative.setVisibility(View.GONE);
548        } else {
549            mButtonNegative.setText(mButtonNegativeText);
550            mButtonNegative.setVisibility(View.VISIBLE);
551
552            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
553        }
554
555        mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
556        mButtonNeutral.setOnClickListener(mButtonHandler);
557
558        if (TextUtils.isEmpty(mButtonNeutralText)) {
559            mButtonNeutral.setVisibility(View.GONE);
560        } else {
561            mButtonNeutral.setText(mButtonNeutralText);
562            mButtonNeutral.setVisibility(View.VISIBLE);
563
564            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
565        }
566
567        if (shouldCenterSingleButton(mContext)) {
568            /*
569             * If we only have 1 button it should be centered on the layout and
570             * expand to fill 50% of the available space.
571             */
572            if (whichButtons == BIT_BUTTON_POSITIVE) {
573                centerButton(mButtonPositive);
574            } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
575                centerButton(mButtonNeutral);
576            } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
577                centerButton(mButtonNeutral);
578            }
579        }
580
581        return whichButtons != 0;
582    }
583
584    private void centerButton(Button button) {
585        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
586        params.gravity = Gravity.CENTER_HORIZONTAL;
587        params.weight = 0.5f;
588        button.setLayoutParams(params);
589        View leftSpacer = mWindow.findViewById(R.id.leftSpacer);
590        if (leftSpacer != null) {
591            leftSpacer.setVisibility(View.VISIBLE);
592        }
593        View rightSpacer = mWindow.findViewById(R.id.rightSpacer);
594        if (rightSpacer != null) {
595            rightSpacer.setVisibility(View.VISIBLE);
596        }
597    }
598
599    private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
600            View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle,
601            View buttonPanel) {
602
603        /* Get all the different background required */
604        int fullDark = a.getResourceId(
605                R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
606        int topDark = a.getResourceId(
607                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
608        int centerDark = a.getResourceId(
609                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
610        int bottomDark = a.getResourceId(
611                R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
612        int fullBright = a.getResourceId(
613                R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
614        int topBright = a.getResourceId(
615                R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
616        int centerBright = a.getResourceId(
617                R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
618        int bottomBright = a.getResourceId(
619                R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
620        int bottomMedium = a.getResourceId(
621                R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
622
623        /*
624         * We now set the background of all of the sections of the alert.
625         * First collect together each section that is being displayed along
626         * with whether it is on a light or dark background, then run through
627         * them setting their backgrounds.  This is complicated because we need
628         * to correctly use the full, top, middle, and bottom graphics depending
629         * on how many views they are and where they appear.
630         */
631
632        View[] views = new View[4];
633        boolean[] light = new boolean[4];
634        View lastView = null;
635        boolean lastLight = false;
636
637        int pos = 0;
638        if (hasTitle) {
639            views[pos] = topPanel;
640            light[pos] = false;
641            pos++;
642        }
643
644        /* The contentPanel displays either a custom text message or
645         * a ListView. If it's text we should use the dark background
646         * for ListView we should use the light background. If neither
647         * are there the contentPanel will be hidden so set it as null.
648         */
649        views[pos] = (contentPanel.getVisibility() == View.GONE)
650                ? null : contentPanel;
651        light[pos] = mListView != null;
652        pos++;
653        if (customPanel != null) {
654            views[pos] = customPanel;
655            light[pos] = mForceInverseBackground;
656            pos++;
657        }
658        if (hasButtons) {
659            views[pos] = buttonPanel;
660            light[pos] = true;
661        }
662
663        boolean setView = false;
664        for (pos=0; pos<views.length; pos++) {
665            View v = views[pos];
666            if (v == null) {
667                continue;
668            }
669            if (lastView != null) {
670                if (!setView) {
671                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
672                } else {
673                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
674                }
675                setView = true;
676            }
677            lastView = v;
678            lastLight = light[pos];
679        }
680
681        if (lastView != null) {
682            if (setView) {
683
684                /* ListViews will use the Bright background but buttons use
685                 * the Medium background.
686                 */
687                lastView.setBackgroundResource(
688                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
689            } else {
690                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
691            }
692        }
693
694        /* TODO: uncomment section below. The logic for this should be if
695         * it's a Contextual menu being displayed AND only a Cancel button
696         * is shown then do this.
697         */
698//        if (hasButtons && (mListView != null)) {
699
700            /* Yet another *special* case. If there is a ListView with buttons
701             * don't put the buttons on the bottom but instead put them in the
702             * footer of the ListView this will allow more items to be
703             * displayed.
704             */
705
706            /*
707            contentPanel.setBackgroundResource(bottomBright);
708            buttonPanel.setBackgroundResource(centerMedium);
709            ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
710            parent.removeView(buttonPanel);
711            AbsListView.LayoutParams params = new AbsListView.LayoutParams(
712                    AbsListView.LayoutParams.MATCH_PARENT,
713                    AbsListView.LayoutParams.MATCH_PARENT);
714            buttonPanel.setLayoutParams(params);
715            mListView.addFooterView(buttonPanel);
716            */
717//        }
718
719        if ((mListView != null) && (mAdapter != null)) {
720            mListView.setAdapter(mAdapter);
721            if (mCheckedItem > -1) {
722                mListView.setItemChecked(mCheckedItem, true);
723                mListView.setSelection(mCheckedItem);
724            }
725        }
726    }
727
728    public static class RecycleListView extends ListView {
729        boolean mRecycleOnMeasure = true;
730
731        public RecycleListView(Context context) {
732            super(context);
733        }
734
735        public RecycleListView(Context context, AttributeSet attrs) {
736            super(context, attrs);
737        }
738
739        public RecycleListView(Context context, AttributeSet attrs, int defStyle) {
740            super(context, attrs, defStyle);
741        }
742
743        @Override
744        protected boolean recycleOnMeasure() {
745            return mRecycleOnMeasure;
746        }
747    }
748
749    public static class AlertParams {
750        public final Context mContext;
751        public final LayoutInflater mInflater;
752
753        public int mIconId = 0;
754        public Drawable mIcon;
755        public int mIconAttrId = 0;
756        public CharSequence mTitle;
757        public View mCustomTitleView;
758        public CharSequence mMessage;
759        public CharSequence mPositiveButtonText;
760        public DialogInterface.OnClickListener mPositiveButtonListener;
761        public CharSequence mNegativeButtonText;
762        public DialogInterface.OnClickListener mNegativeButtonListener;
763        public CharSequence mNeutralButtonText;
764        public DialogInterface.OnClickListener mNeutralButtonListener;
765        public boolean mCancelable;
766        public DialogInterface.OnCancelListener mOnCancelListener;
767        public DialogInterface.OnDismissListener mOnDismissListener;
768        public DialogInterface.OnKeyListener mOnKeyListener;
769        public CharSequence[] mItems;
770        public ListAdapter mAdapter;
771        public DialogInterface.OnClickListener mOnClickListener;
772        public View mView;
773        public int mViewSpacingLeft;
774        public int mViewSpacingTop;
775        public int mViewSpacingRight;
776        public int mViewSpacingBottom;
777        public boolean mViewSpacingSpecified = false;
778        public boolean[] mCheckedItems;
779        public boolean mIsMultiChoice;
780        public boolean mIsSingleChoice;
781        public int mCheckedItem = -1;
782        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
783        public Cursor mCursor;
784        public String mLabelColumn;
785        public String mIsCheckedColumn;
786        public boolean mForceInverseBackground;
787        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
788        public OnPrepareListViewListener mOnPrepareListViewListener;
789        public boolean mRecycleOnMeasure = true;
790
791        /**
792         * Interface definition for a callback to be invoked before the ListView
793         * will be bound to an adapter.
794         */
795        public interface OnPrepareListViewListener {
796
797            /**
798             * Called before the ListView is bound to an adapter.
799             * @param listView The ListView that will be shown in the dialog.
800             */
801            void onPrepareListView(ListView listView);
802        }
803
804        public AlertParams(Context context) {
805            mContext = context;
806            mCancelable = true;
807            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
808        }
809
810        public void apply(AlertController dialog) {
811            if (mCustomTitleView != null) {
812                dialog.setCustomTitle(mCustomTitleView);
813            } else {
814                if (mTitle != null) {
815                    dialog.setTitle(mTitle);
816                }
817                if (mIcon != null) {
818                    dialog.setIcon(mIcon);
819                }
820                if (mIconId >= 0) {
821                    dialog.setIcon(mIconId);
822                }
823                if (mIconAttrId > 0) {
824                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
825                }
826            }
827            if (mMessage != null) {
828                dialog.setMessage(mMessage);
829            }
830            if (mPositiveButtonText != null) {
831                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
832                        mPositiveButtonListener, null);
833            }
834            if (mNegativeButtonText != null) {
835                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
836                        mNegativeButtonListener, null);
837            }
838            if (mNeutralButtonText != null) {
839                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
840                        mNeutralButtonListener, null);
841            }
842            if (mForceInverseBackground) {
843                dialog.setInverseBackgroundForced(true);
844            }
845            // For a list, the client can either supply an array of items or an
846            // adapter or a cursor
847            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
848                createListView(dialog);
849            }
850            if (mView != null) {
851                if (mViewSpacingSpecified) {
852                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
853                            mViewSpacingBottom);
854                } else {
855                    dialog.setView(mView);
856                }
857            }
858
859            /*
860            dialog.setCancelable(mCancelable);
861            dialog.setOnCancelListener(mOnCancelListener);
862            if (mOnKeyListener != null) {
863                dialog.setOnKeyListener(mOnKeyListener);
864            }
865            */
866        }
867
868        private void createListView(final AlertController dialog) {
869            final RecycleListView listView = (RecycleListView)
870                    mInflater.inflate(dialog.mListLayout, null);
871            ListAdapter adapter;
872
873            if (mIsMultiChoice) {
874                if (mCursor == null) {
875                    adapter = new ArrayAdapter<CharSequence>(
876                            mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) {
877                        @Override
878                        public View getView(int position, View convertView, ViewGroup parent) {
879                            View view = super.getView(position, convertView, parent);
880                            if (mCheckedItems != null) {
881                                boolean isItemChecked = mCheckedItems[position];
882                                if (isItemChecked) {
883                                    listView.setItemChecked(position, true);
884                                }
885                            }
886                            return view;
887                        }
888                    };
889                } else {
890                    adapter = new CursorAdapter(mContext, mCursor, false) {
891                        private final int mLabelIndex;
892                        private final int mIsCheckedIndex;
893
894                        {
895                            final Cursor cursor = getCursor();
896                            mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
897                            mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
898                        }
899
900                        @Override
901                        public void bindView(View view, Context context, Cursor cursor) {
902                            CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
903                            text.setText(cursor.getString(mLabelIndex));
904                            listView.setItemChecked(cursor.getPosition(),
905                                    cursor.getInt(mIsCheckedIndex) == 1);
906                        }
907
908                        @Override
909                        public View newView(Context context, Cursor cursor, ViewGroup parent) {
910                            return mInflater.inflate(dialog.mMultiChoiceItemLayout,
911                                    parent, false);
912                        }
913
914                    };
915                }
916            } else {
917                int layout = mIsSingleChoice
918                        ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout;
919                if (mCursor == null) {
920                    adapter = (mAdapter != null) ? mAdapter
921                            : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems);
922                } else {
923                    adapter = new SimpleCursorAdapter(mContext, layout,
924                            mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1});
925                }
926            }
927
928            if (mOnPrepareListViewListener != null) {
929                mOnPrepareListViewListener.onPrepareListView(listView);
930            }
931
932            /* Don't directly set the adapter on the ListView as we might
933             * want to add a footer to the ListView later.
934             */
935            dialog.mAdapter = adapter;
936            dialog.mCheckedItem = mCheckedItem;
937
938            if (mOnClickListener != null) {
939                listView.setOnItemClickListener(new OnItemClickListener() {
940                    public void onItemClick(AdapterView parent, View v, int position, long id) {
941                        mOnClickListener.onClick(dialog.mDialogInterface, position);
942                        if (!mIsSingleChoice) {
943                            dialog.mDialogInterface.dismiss();
944                        }
945                    }
946                });
947            } else if (mOnCheckboxClickListener != null) {
948                listView.setOnItemClickListener(new OnItemClickListener() {
949                    public void onItemClick(AdapterView parent, View v, int position, long id) {
950                        if (mCheckedItems != null) {
951                            mCheckedItems[position] = listView.isItemChecked(position);
952                        }
953                        mOnCheckboxClickListener.onClick(
954                                dialog.mDialogInterface, position, listView.isItemChecked(position));
955                    }
956                });
957            }
958
959            // Attach a given OnItemSelectedListener to the ListView
960            if (mOnItemSelectedListener != null) {
961                listView.setOnItemSelectedListener(mOnItemSelectedListener);
962            }
963
964            if (mIsSingleChoice) {
965                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
966            } else if (mIsMultiChoice) {
967                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
968            }
969            listView.mRecycleOnMeasure = mRecycleOnMeasure;
970            dialog.mListView = listView;
971        }
972    }
973
974}
975