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