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