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