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