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