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