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