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