AlertController.java revision ec186706df72fbb62bfe813d83cff9167dd95cb4
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.Window;
41import android.view.WindowManager;
42import android.widget.AdapterView;
43import android.widget.AdapterView.OnItemClickListener;
44import android.widget.ArrayAdapter;
45import android.widget.Button;
46import android.widget.CheckedTextView;
47import android.widget.CursorAdapter;
48import android.widget.FrameLayout;
49import android.widget.ImageView;
50import android.widget.LinearLayout;
51import android.widget.ListAdapter;
52import android.widget.ListView;
53import android.widget.ScrollView;
54import android.widget.SimpleCursorAdapter;
55import android.widget.TextView;
56
57import java.lang.ref.WeakReference;
58
59public class AlertController {
60
61    private final Context mContext;
62    private final DialogInterface mDialogInterface;
63    private final Window mWindow;
64
65    private CharSequence mTitle;
66
67    private CharSequence mMessage;
68
69    private ListView mListView;
70
71    private View mView;
72
73    private int mViewLayoutResId;
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        mWindow.setContentView(mAlertDialogLayout);
238        setupView();
239    }
240
241    public void setTitle(CharSequence title) {
242        mTitle = title;
243        if (mTitleView != null) {
244            mTitleView.setText(title);
245        }
246    }
247
248    /**
249     * @see AlertDialog.Builder#setCustomTitle(View)
250     */
251    public void setCustomTitle(View customTitleView) {
252        mCustomTitleView = customTitleView;
253    }
254
255    public void setMessage(CharSequence message) {
256        mMessage = message;
257        if (mMessageView != null) {
258            mMessageView.setText(message);
259        }
260    }
261
262    /**
263     * Set the view resource to display in the dialog.
264     */
265    public void setView(int layoutResId) {
266        mView = null;
267        mViewLayoutResId = layoutResId;
268        mViewSpacingSpecified = false;
269    }
270
271    /**
272     * Set the view to display in the dialog.
273     */
274    public void setView(View view) {
275        mView = view;
276        mViewLayoutResId = 0;
277        mViewSpacingSpecified = false;
278    }
279
280    /**
281     * Set the view to display in the dialog along with the spacing around that view
282     */
283    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
284            int viewSpacingBottom) {
285        mView = view;
286        mViewLayoutResId = 0;
287        mViewSpacingSpecified = true;
288        mViewSpacingLeft = viewSpacingLeft;
289        mViewSpacingTop = viewSpacingTop;
290        mViewSpacingRight = viewSpacingRight;
291        mViewSpacingBottom = viewSpacingBottom;
292    }
293
294    /**
295     * Sets a click listener or a message to be sent when the button is clicked.
296     * You only need to pass one of {@code listener} or {@code msg}.
297     *
298     * @param whichButton Which button, can be one of
299     *            {@link DialogInterface#BUTTON_POSITIVE},
300     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
301     *            {@link DialogInterface#BUTTON_NEUTRAL}
302     * @param text The text to display in positive button.
303     * @param listener The {@link DialogInterface.OnClickListener} to use.
304     * @param msg The {@link Message} to be sent when clicked.
305     */
306    public void setButton(int whichButton, CharSequence text,
307            DialogInterface.OnClickListener listener, Message msg) {
308
309        if (msg == null && listener != null) {
310            msg = mHandler.obtainMessage(whichButton, listener);
311        }
312
313        switch (whichButton) {
314
315            case DialogInterface.BUTTON_POSITIVE:
316                mButtonPositiveText = text;
317                mButtonPositiveMessage = msg;
318                break;
319
320            case DialogInterface.BUTTON_NEGATIVE:
321                mButtonNegativeText = text;
322                mButtonNegativeMessage = msg;
323                break;
324
325            case DialogInterface.BUTTON_NEUTRAL:
326                mButtonNeutralText = text;
327                mButtonNeutralMessage = msg;
328                break;
329
330            default:
331                throw new IllegalArgumentException("Button does not exist");
332        }
333    }
334
335    /**
336     * Set resId to 0 if you don't want an icon.
337     * @param resId the resourceId of the drawable to use as the icon or 0
338     * if you don't want an icon.
339     */
340    public void setIcon(int resId) {
341        mIconId = resId;
342        if (mIconView != null) {
343            if (resId > 0) {
344                mIconView.setImageResource(mIconId);
345            } else if (resId == 0) {
346                mIconView.setVisibility(View.GONE);
347            }
348        }
349    }
350
351    public void setIcon(Drawable icon) {
352        mIcon = icon;
353        if ((mIconView != null) && (mIcon != null)) {
354            mIconView.setImageDrawable(icon);
355        }
356    }
357
358    /**
359     * @param attrId the attributeId of the theme-specific drawable
360     * to resolve the resourceId for.
361     *
362     * @return resId the resourceId of the theme-specific drawable
363     */
364    public int getIconAttributeResId(int attrId) {
365        TypedValue out = new TypedValue();
366        mContext.getTheme().resolveAttribute(attrId, out, true);
367        return out.resourceId;
368    }
369
370    public void setInverseBackgroundForced(boolean forceInverseBackground) {
371        mForceInverseBackground = forceInverseBackground;
372    }
373
374    public ListView getListView() {
375        return mListView;
376    }
377
378    public Button getButton(int whichButton) {
379        switch (whichButton) {
380            case DialogInterface.BUTTON_POSITIVE:
381                return mButtonPositive;
382            case DialogInterface.BUTTON_NEGATIVE:
383                return mButtonNegative;
384            case DialogInterface.BUTTON_NEUTRAL:
385                return mButtonNeutral;
386            default:
387                return null;
388        }
389    }
390
391    @SuppressWarnings({"UnusedDeclaration"})
392    public boolean onKeyDown(int keyCode, KeyEvent event) {
393        return mScrollView != null && mScrollView.executeKeyEvent(event);
394    }
395
396    @SuppressWarnings({"UnusedDeclaration"})
397    public boolean onKeyUp(int keyCode, KeyEvent event) {
398        return mScrollView != null && mScrollView.executeKeyEvent(event);
399    }
400
401    private void setupView() {
402        LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
403        setupContent(contentPanel);
404        boolean hasButtons = setupButtons();
405
406        LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
407        TypedArray a = mContext.obtainStyledAttributes(
408                null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
409        boolean hasTitle = setupTitle(topPanel);
410
411        View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
412        if (!hasButtons) {
413            buttonPanel.setVisibility(View.GONE);
414            mWindow.setCloseOnTouchOutsideIfNotSet(true);
415        }
416
417        final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
418        final View customView;
419        if (mView != null) {
420            customView = mView;
421        } else if (mViewLayoutResId != 0) {
422            final LayoutInflater inflater = LayoutInflater.from(mContext);
423            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
424        } else {
425            customView = null;
426        }
427
428        if (customView == null || !canTextInput(customView)) {
429            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
430                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
431        }
432
433        if (customView != null) {
434            final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
435            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
436
437            if (mViewSpacingSpecified) {
438                custom.setPadding(
439                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
440            }
441
442            if (mListView != null) {
443                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
444            }
445        } else {
446            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 || customView != 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 = a.getResourceId(
629                R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
630        int topDark = a.getResourceId(
631                R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
632        int centerDark = a.getResourceId(
633                R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
634        int bottomDark = a.getResourceId(
635                R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
636        int fullBright = a.getResourceId(
637                R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
638        int topBright = a.getResourceId(
639                R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
640        int centerBright = a.getResourceId(
641                R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
642        int bottomBright = a.getResourceId(
643                R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
644        int bottomMedium = a.getResourceId(
645                R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
646
647        /*
648         * We now set the background of all of the sections of the alert.
649         * First collect together each section that is being displayed along
650         * with whether it is on a light or dark background, then run through
651         * them setting their backgrounds.  This is complicated because we need
652         * to correctly use the full, top, middle, and bottom graphics depending
653         * on how many views they are and where they appear.
654         */
655
656        View[] views = new View[4];
657        boolean[] light = new boolean[4];
658        View lastView = null;
659        boolean lastLight = false;
660
661        int pos = 0;
662        if (hasTitle) {
663            views[pos] = topPanel;
664            light[pos] = false;
665            pos++;
666        }
667
668        /* The contentPanel displays either a custom text message or
669         * a ListView. If it's text we should use the dark background
670         * for ListView we should use the light background. If neither
671         * are there the contentPanel will be hidden so set it as null.
672         */
673        views[pos] = (contentPanel.getVisibility() == View.GONE)
674                ? null : contentPanel;
675        light[pos] = mListView != null;
676        pos++;
677        if (customPanel != null) {
678            views[pos] = customPanel;
679            light[pos] = mForceInverseBackground;
680            pos++;
681        }
682        if (hasButtons) {
683            views[pos] = buttonPanel;
684            light[pos] = true;
685        }
686
687        boolean setView = false;
688        for (pos=0; pos<views.length; pos++) {
689            View v = views[pos];
690            if (v == null) {
691                continue;
692            }
693            if (lastView != null) {
694                if (!setView) {
695                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
696                } else {
697                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
698                }
699                setView = true;
700            }
701            lastView = v;
702            lastLight = light[pos];
703        }
704
705        if (lastView != null) {
706            if (setView) {
707
708                /* ListViews will use the Bright background but buttons use
709                 * the Medium background.
710                 */
711                lastView.setBackgroundResource(
712                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
713            } else {
714                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
715            }
716        }
717
718        /* TODO: uncomment section below. The logic for this should be if
719         * it's a Contextual menu being displayed AND only a Cancel button
720         * is shown then do this.
721         */
722//        if (hasButtons && (mListView != null)) {
723
724            /* Yet another *special* case. If there is a ListView with buttons
725             * don't put the buttons on the bottom but instead put them in the
726             * footer of the ListView this will allow more items to be
727             * displayed.
728             */
729
730            /*
731            contentPanel.setBackgroundResource(bottomBright);
732            buttonPanel.setBackgroundResource(centerMedium);
733            ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
734            parent.removeView(buttonPanel);
735            AbsListView.LayoutParams params = new AbsListView.LayoutParams(
736                    AbsListView.LayoutParams.MATCH_PARENT,
737                    AbsListView.LayoutParams.MATCH_PARENT);
738            buttonPanel.setLayoutParams(params);
739            mListView.addFooterView(buttonPanel);
740            */
741//        }
742
743        if ((mListView != null) && (mAdapter != null)) {
744            mListView.setAdapter(mAdapter);
745            if (mCheckedItem > -1) {
746                mListView.setItemChecked(mCheckedItem, true);
747                mListView.setSelection(mCheckedItem);
748            }
749        }
750    }
751
752    public static class RecycleListView extends ListView {
753        boolean mRecycleOnMeasure = true;
754
755        public RecycleListView(Context context) {
756            super(context);
757        }
758
759        public RecycleListView(Context context, AttributeSet attrs) {
760            super(context, attrs);
761        }
762
763        public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) {
764            super(context, attrs, defStyleAttr);
765        }
766
767        public RecycleListView(
768                Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
769            super(context, attrs, defStyleAttr, defStyleRes);
770        }
771
772        @Override
773        protected boolean recycleOnMeasure() {
774            return mRecycleOnMeasure;
775        }
776    }
777
778    public static class AlertParams {
779        public final Context mContext;
780        public final LayoutInflater mInflater;
781
782        public int mIconId = 0;
783        public Drawable mIcon;
784        public int mIconAttrId = 0;
785        public CharSequence mTitle;
786        public View mCustomTitleView;
787        public CharSequence mMessage;
788        public CharSequence mPositiveButtonText;
789        public DialogInterface.OnClickListener mPositiveButtonListener;
790        public CharSequence mNegativeButtonText;
791        public DialogInterface.OnClickListener mNegativeButtonListener;
792        public CharSequence mNeutralButtonText;
793        public DialogInterface.OnClickListener mNeutralButtonListener;
794        public boolean mCancelable;
795        public DialogInterface.OnCancelListener mOnCancelListener;
796        public DialogInterface.OnDismissListener mOnDismissListener;
797        public DialogInterface.OnKeyListener mOnKeyListener;
798        public CharSequence[] mItems;
799        public ListAdapter mAdapter;
800        public DialogInterface.OnClickListener mOnClickListener;
801        public int mViewLayoutResId;
802        public View mView;
803        public int mViewSpacingLeft;
804        public int mViewSpacingTop;
805        public int mViewSpacingRight;
806        public int mViewSpacingBottom;
807        public boolean mViewSpacingSpecified = false;
808        public boolean[] mCheckedItems;
809        public boolean mIsMultiChoice;
810        public boolean mIsSingleChoice;
811        public int mCheckedItem = -1;
812        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
813        public Cursor mCursor;
814        public String mLabelColumn;
815        public String mIsCheckedColumn;
816        public boolean mForceInverseBackground;
817        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
818        public OnPrepareListViewListener mOnPrepareListViewListener;
819        public boolean mRecycleOnMeasure = true;
820
821        /**
822         * Interface definition for a callback to be invoked before the ListView
823         * will be bound to an adapter.
824         */
825        public interface OnPrepareListViewListener {
826
827            /**
828             * Called before the ListView is bound to an adapter.
829             * @param listView The ListView that will be shown in the dialog.
830             */
831            void onPrepareListView(ListView listView);
832        }
833
834        public AlertParams(Context context) {
835            mContext = context;
836            mCancelable = true;
837            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
838        }
839
840        public void apply(AlertController dialog) {
841            if (mCustomTitleView != null) {
842                dialog.setCustomTitle(mCustomTitleView);
843            } else {
844                if (mTitle != null) {
845                    dialog.setTitle(mTitle);
846                }
847                if (mIcon != null) {
848                    dialog.setIcon(mIcon);
849                }
850                if (mIconId >= 0) {
851                    dialog.setIcon(mIconId);
852                }
853                if (mIconAttrId > 0) {
854                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
855                }
856            }
857            if (mMessage != null) {
858                dialog.setMessage(mMessage);
859            }
860            if (mPositiveButtonText != null) {
861                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
862                        mPositiveButtonListener, null);
863            }
864            if (mNegativeButtonText != null) {
865                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
866                        mNegativeButtonListener, null);
867            }
868            if (mNeutralButtonText != null) {
869                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
870                        mNeutralButtonListener, null);
871            }
872            if (mForceInverseBackground) {
873                dialog.setInverseBackgroundForced(true);
874            }
875            // For a list, the client can either supply an array of items or an
876            // adapter or a cursor
877            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
878                createListView(dialog);
879            }
880            if (mView != null) {
881                if (mViewSpacingSpecified) {
882                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
883                            mViewSpacingBottom);
884                } else {
885                    dialog.setView(mView);
886                }
887            } else if (mViewLayoutResId != 0) {
888                dialog.setView(mViewLayoutResId);
889            }
890
891            /*
892            dialog.setCancelable(mCancelable);
893            dialog.setOnCancelListener(mOnCancelListener);
894            if (mOnKeyListener != null) {
895                dialog.setOnKeyListener(mOnKeyListener);
896            }
897            */
898        }
899
900        private void createListView(final AlertController dialog) {
901            final RecycleListView listView = (RecycleListView)
902                    mInflater.inflate(dialog.mListLayout, null);
903            ListAdapter adapter;
904
905            if (mIsMultiChoice) {
906                if (mCursor == null) {
907                    adapter = new ArrayAdapter<CharSequence>(
908                            mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) {
909                        @Override
910                        public View getView(int position, View convertView, ViewGroup parent) {
911                            View view = super.getView(position, convertView, parent);
912                            if (mCheckedItems != null) {
913                                boolean isItemChecked = mCheckedItems[position];
914                                if (isItemChecked) {
915                                    listView.setItemChecked(position, true);
916                                }
917                            }
918                            return view;
919                        }
920                    };
921                } else {
922                    adapter = new CursorAdapter(mContext, mCursor, false) {
923                        private final int mLabelIndex;
924                        private final int mIsCheckedIndex;
925
926                        {
927                            final Cursor cursor = getCursor();
928                            mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
929                            mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
930                        }
931
932                        @Override
933                        public void bindView(View view, Context context, Cursor cursor) {
934                            CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
935                            text.setText(cursor.getString(mLabelIndex));
936                            listView.setItemChecked(cursor.getPosition(),
937                                    cursor.getInt(mIsCheckedIndex) == 1);
938                        }
939
940                        @Override
941                        public View newView(Context context, Cursor cursor, ViewGroup parent) {
942                            return mInflater.inflate(dialog.mMultiChoiceItemLayout,
943                                    parent, false);
944                        }
945
946                    };
947                }
948            } else {
949                int layout = mIsSingleChoice
950                        ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout;
951                if (mCursor == null) {
952                    adapter = (mAdapter != null) ? mAdapter
953                            : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems);
954                } else {
955                    adapter = new SimpleCursorAdapter(mContext, layout,
956                            mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1});
957                }
958            }
959
960            if (mOnPrepareListViewListener != null) {
961                mOnPrepareListViewListener.onPrepareListView(listView);
962            }
963
964            /* Don't directly set the adapter on the ListView as we might
965             * want to add a footer to the ListView later.
966             */
967            dialog.mAdapter = adapter;
968            dialog.mCheckedItem = mCheckedItem;
969
970            if (mOnClickListener != null) {
971                listView.setOnItemClickListener(new OnItemClickListener() {
972                    public void onItemClick(AdapterView parent, View v, int position, long id) {
973                        mOnClickListener.onClick(dialog.mDialogInterface, position);
974                        if (!mIsSingleChoice) {
975                            dialog.mDialogInterface.dismiss();
976                        }
977                    }
978                });
979            } else if (mOnCheckboxClickListener != null) {
980                listView.setOnItemClickListener(new OnItemClickListener() {
981                    public void onItemClick(AdapterView parent, View v, int position, long id) {
982                        if (mCheckedItems != null) {
983                            mCheckedItems[position] = listView.isItemChecked(position);
984                        }
985                        mOnCheckboxClickListener.onClick(
986                                dialog.mDialogInterface, position, listView.isItemChecked(position));
987                    }
988                });
989            }
990
991            // Attach a given OnItemSelectedListener to the ListView
992            if (mOnItemSelectedListener != null) {
993                listView.setOnItemSelectedListener(mOnItemSelectedListener);
994            }
995
996            if (mIsSingleChoice) {
997                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
998            } else if (mIsMultiChoice) {
999                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
1000            }
1001            listView.mRecycleOnMeasure = mRecycleOnMeasure;
1002            dialog.mListView = listView;
1003        }
1004    }
1005
1006}
1007