AlertController.java revision 58c14e5c83aa8f836aff6e48ad90e6976948f1ac
1/*
2 * Copyright (C) 2015 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 android.support.v7.app;
18
19import android.content.Context;
20import android.content.DialogInterface;
21import android.content.res.TypedArray;
22import android.database.Cursor;
23import android.graphics.drawable.Drawable;
24import android.os.Build;
25import android.os.Handler;
26import android.os.Message;
27import android.support.annotation.Nullable;
28import android.support.v4.view.ViewCompat;
29import android.support.v4.widget.NestedScrollView;
30import android.support.v7.appcompat.R;
31import android.text.TextUtils;
32import android.util.TypedValue;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.ViewGroup.LayoutParams;
38import android.view.ViewParent;
39import android.view.ViewStub;
40import android.view.Window;
41import android.view.WindowManager;
42import android.widget.AbsListView;
43import android.widget.AdapterView;
44import android.widget.AdapterView.OnItemClickListener;
45import android.widget.ArrayAdapter;
46import android.widget.Button;
47import android.widget.CheckedTextView;
48import android.widget.CursorAdapter;
49import android.widget.FrameLayout;
50import android.widget.ImageView;
51import android.widget.LinearLayout;
52import android.widget.ListAdapter;
53import android.widget.ListView;
54import android.widget.SimpleCursorAdapter;
55import android.widget.TextView;
56
57import java.lang.ref.WeakReference;
58
59import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
60
61class AlertController {
62    private final Context mContext;
63    private final AppCompatDialog mDialog;
64    private final Window mWindow;
65
66    private CharSequence mTitle;
67    private CharSequence mMessage;
68    private ListView mListView;
69    private View mView;
70
71    private int mViewLayoutResId;
72
73    private int mViewSpacingLeft;
74    private int mViewSpacingTop;
75    private int mViewSpacingRight;
76    private int mViewSpacingBottom;
77    private boolean mViewSpacingSpecified = false;
78
79    private Button mButtonPositive;
80    private CharSequence mButtonPositiveText;
81    private Message mButtonPositiveMessage;
82
83    private Button mButtonNegative;
84    private CharSequence mButtonNegativeText;
85    private Message mButtonNegativeMessage;
86
87    private Button mButtonNeutral;
88    private CharSequence mButtonNeutralText;
89    private Message mButtonNeutralMessage;
90
91    private NestedScrollView mScrollView;
92
93    private int mIconId = 0;
94    private Drawable mIcon;
95
96    private ImageView mIconView;
97    private TextView mTitleView;
98    private TextView mMessageView;
99    private View mCustomTitleView;
100
101    private ListAdapter mAdapter;
102
103    private int mCheckedItem = -1;
104
105    private int mAlertDialogLayout;
106    private int mButtonPanelSideLayout;
107    private int mListLayout;
108    private int mMultiChoiceItemLayout;
109    private int mSingleChoiceItemLayout;
110    private int mListItemLayout;
111
112    private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
113
114    private Handler mHandler;
115
116    private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
117        @Override
118        public void onClick(View v) {
119            final Message m;
120            if (v == mButtonPositive && mButtonPositiveMessage != null) {
121                m = Message.obtain(mButtonPositiveMessage);
122            } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
123                m = Message.obtain(mButtonNegativeMessage);
124            } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
125                m = Message.obtain(mButtonNeutralMessage);
126            } else {
127                m = null;
128            }
129
130            if (m != null) {
131                m.sendToTarget();
132            }
133
134            // Post a message so we dismiss after the above handlers are executed
135            mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog)
136                    .sendToTarget();
137        }
138    };
139
140    private static final class ButtonHandler extends Handler {
141        // Button clicks have Message.what as the BUTTON{1,2,3} constant
142        private static final int MSG_DISMISS_DIALOG = 1;
143
144        private WeakReference<DialogInterface> mDialog;
145
146        public ButtonHandler(DialogInterface dialog) {
147            mDialog = new WeakReference<>(dialog);
148        }
149
150        @Override
151        public void handleMessage(Message msg) {
152            switch (msg.what) {
153
154                case DialogInterface.BUTTON_POSITIVE:
155                case DialogInterface.BUTTON_NEGATIVE:
156                case DialogInterface.BUTTON_NEUTRAL:
157                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
158                    break;
159
160                case MSG_DISMISS_DIALOG:
161                    ((DialogInterface) msg.obj).dismiss();
162            }
163        }
164    }
165
166    public AlertController(Context context, AppCompatDialog di, Window window) {
167        mContext = context;
168        mDialog = di;
169        mWindow = window;
170        mHandler = new ButtonHandler(di);
171
172        final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog,
173                R.attr.alertDialogStyle, 0);
174
175        mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0);
176        mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0);
177
178        mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
179        mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);
180        mSingleChoiceItemLayout = a
181                .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
182        mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
183
184        a.recycle();
185    }
186
187    static boolean canTextInput(View v) {
188        if (v.onCheckIsTextEditor()) {
189            return true;
190        }
191
192        if (!(v instanceof ViewGroup)) {
193            return false;
194        }
195
196        ViewGroup vg = (ViewGroup) v;
197        int i = vg.getChildCount();
198        while (i > 0) {
199            i--;
200            v = vg.getChildAt(i);
201            if (canTextInput(v)) {
202                return true;
203            }
204        }
205
206        return false;
207    }
208
209    public void installContent() {
210        /* We use a custom title so never request a window title */
211        mDialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
212        final int contentView = selectContentView();
213        mDialog.setContentView(contentView);
214        setupView();
215    }
216
217    private int selectContentView() {
218        if (mButtonPanelSideLayout == 0) {
219            return mAlertDialogLayout;
220        }
221        if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
222            return mButtonPanelSideLayout;
223        }
224        return mAlertDialogLayout;
225    }
226
227    public void setTitle(CharSequence title) {
228        mTitle = title;
229        if (mTitleView != null) {
230            mTitleView.setText(title);
231        }
232    }
233
234    /**
235     * @see AlertDialog.Builder#setCustomTitle(View)
236     */
237    public void setCustomTitle(View customTitleView) {
238        mCustomTitleView = customTitleView;
239    }
240
241    public void setMessage(CharSequence message) {
242        mMessage = message;
243        if (mMessageView != null) {
244            mMessageView.setText(message);
245        }
246    }
247
248    /**
249     * Set the view resource to display in the dialog.
250     */
251    public void setView(int layoutResId) {
252        mView = null;
253        mViewLayoutResId = layoutResId;
254        mViewSpacingSpecified = false;
255    }
256
257    /**
258     * Set the view to display in the dialog.
259     */
260    public void setView(View view) {
261        mView = view;
262        mViewLayoutResId = 0;
263        mViewSpacingSpecified = false;
264    }
265
266    /**
267     * Set the view to display in the dialog along with the spacing around that view
268     */
269    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
270            int viewSpacingBottom) {
271        mView = view;
272        mViewLayoutResId = 0;
273        mViewSpacingSpecified = true;
274        mViewSpacingLeft = viewSpacingLeft;
275        mViewSpacingTop = viewSpacingTop;
276        mViewSpacingRight = viewSpacingRight;
277        mViewSpacingBottom = viewSpacingBottom;
278    }
279
280    /**
281     * Sets a hint for the best button panel layout.
282     */
283    public void setButtonPanelLayoutHint(int layoutHint) {
284        mButtonPanelLayoutHint = layoutHint;
285    }
286
287    /**
288     * Sets a click listener or a message to be sent when the button is clicked.
289     * You only need to pass one of {@code listener} or {@code msg}.
290     *
291     * @param whichButton Which button, can be one of
292     *                    {@link DialogInterface#BUTTON_POSITIVE},
293     *                    {@link DialogInterface#BUTTON_NEGATIVE}, or
294     *                    {@link DialogInterface#BUTTON_NEUTRAL}
295     * @param text        The text to display in positive button.
296     * @param listener    The {@link DialogInterface.OnClickListener} to use.
297     * @param msg         The {@link Message} to be sent when clicked.
298     */
299    public void setButton(int whichButton, CharSequence text,
300            DialogInterface.OnClickListener listener, Message msg) {
301
302        if (msg == null && listener != null) {
303            msg = mHandler.obtainMessage(whichButton, listener);
304        }
305
306        switch (whichButton) {
307
308            case DialogInterface.BUTTON_POSITIVE:
309                mButtonPositiveText = text;
310                mButtonPositiveMessage = msg;
311                break;
312
313            case DialogInterface.BUTTON_NEGATIVE:
314                mButtonNegativeText = text;
315                mButtonNegativeMessage = msg;
316                break;
317
318            case DialogInterface.BUTTON_NEUTRAL:
319                mButtonNeutralText = text;
320                mButtonNeutralMessage = msg;
321                break;
322
323            default:
324                throw new IllegalArgumentException("Button does not exist");
325        }
326    }
327
328    /**
329     * Specifies the icon to display next to the alert title.
330     *
331     * @param resId the resource identifier of the drawable to use as the icon,
332     *              or 0 for no icon
333     */
334    public void setIcon(int resId) {
335        mIcon = null;
336        mIconId = resId;
337
338        if (mIconView != null) {
339            if (resId != 0) {
340                mIconView.setVisibility(View.VISIBLE);
341                mIconView.setImageResource(mIconId);
342            } else {
343                mIconView.setVisibility(View.GONE);
344            }
345        }
346    }
347
348    /**
349     * Specifies the icon to display next to the alert title.
350     *
351     * @param icon the drawable to use as the icon or null for no icon
352     */
353    public void setIcon(Drawable icon) {
354        mIcon = icon;
355        mIconId = 0;
356
357        if (mIconView != null) {
358            if (icon != null) {
359                mIconView.setVisibility(View.VISIBLE);
360                mIconView.setImageDrawable(icon);
361            } else {
362                mIconView.setVisibility(View.GONE);
363            }
364        }
365    }
366
367    /**
368     * @param attrId the attributeId of the theme-specific drawable
369     *               to resolve the resourceId for.
370     *
371     * @return resId the resourceId of the theme-specific drawable
372     */
373    public int getIconAttributeResId(int attrId) {
374        TypedValue out = new TypedValue();
375        mContext.getTheme().resolveAttribute(attrId, out, true);
376        return out.resourceId;
377    }
378
379    public ListView getListView() {
380        return mListView;
381    }
382
383    public Button getButton(int whichButton) {
384        switch (whichButton) {
385            case DialogInterface.BUTTON_POSITIVE:
386                return mButtonPositive;
387            case DialogInterface.BUTTON_NEGATIVE:
388                return mButtonNegative;
389            case DialogInterface.BUTTON_NEUTRAL:
390                return mButtonNeutral;
391            default:
392                return null;
393        }
394    }
395
396    @SuppressWarnings({"UnusedDeclaration"})
397    public boolean onKeyDown(int keyCode, KeyEvent event) {
398        return mScrollView != null && mScrollView.executeKeyEvent(event);
399    }
400
401    @SuppressWarnings({"UnusedDeclaration"})
402    public boolean onKeyUp(int keyCode, KeyEvent event) {
403        return mScrollView != null && mScrollView.executeKeyEvent(event);
404    }
405
406    /**
407     * Resolves whether a custom or default panel should be used. Removes the
408     * default panel if a custom panel should be used. If the resolved panel is
409     * a view stub, inflates before returning.
410     *
411     * @param customPanel the custom panel
412     * @param defaultPanel the default panel
413     * @return the panel to use
414     */
415    @Nullable
416    private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
417        if (customPanel == null) {
418            // Inflate the default panel, if needed.
419            if (defaultPanel instanceof ViewStub) {
420                defaultPanel = ((ViewStub) defaultPanel).inflate();
421            }
422
423            return (ViewGroup) defaultPanel;
424        }
425
426        // Remove the default panel entirely.
427        if (defaultPanel != null) {
428            final ViewParent parent = defaultPanel.getParent();
429            if (parent instanceof ViewGroup) {
430                ((ViewGroup) parent).removeView(defaultPanel);
431            }
432        }
433
434        // Inflate the custom panel, if needed.
435        if (customPanel instanceof ViewStub) {
436            customPanel = ((ViewStub) customPanel).inflate();
437        }
438
439        return (ViewGroup) customPanel;
440    }
441
442    private void setupView() {
443        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
444        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
445        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
446        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
447
448        // Install custom content before setting up the title or buttons so
449        // that we can handle panel overrides.
450        final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
451        setupCustomContent(customPanel);
452
453        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
454        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
455        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
456
457        // Resolve the correct panels and remove the defaults, if needed.
458        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
459        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
460        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
461
462        setupContent(contentPanel);
463        setupButtons(buttonPanel);
464        setupTitle(topPanel);
465
466        final boolean hasCustomPanel = customPanel != null
467                && customPanel.getVisibility() != View.GONE;
468        final boolean hasTopPanel = topPanel != null
469                && topPanel.getVisibility() != View.GONE;
470        final boolean hasButtonPanel = buttonPanel != null
471                && buttonPanel.getVisibility() != View.GONE;
472
473        // Only display the text spacer if we don't have buttons.
474        if (!hasButtonPanel) {
475            if (contentPanel != null) {
476                final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
477                if (spacer != null) {
478                    spacer.setVisibility(View.VISIBLE);
479                }
480            }
481        }
482
483        if (hasTopPanel) {
484            // Only clip scrolling content to padding if we have a title.
485            if (mScrollView != null) {
486                mScrollView.setClipToPadding(true);
487            }
488        }
489
490        // Update scroll indicators as needed.
491        if (!hasCustomPanel) {
492            final View content = mListView != null ? mListView : mScrollView;
493            if (content != null) {
494                final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0)
495                        | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0);
496                setScrollIndicators(contentPanel, content, indicators,
497                        ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM);
498            }
499        }
500
501        final ListView listView = mListView;
502        if (listView != null && mAdapter != null) {
503            listView.setAdapter(mAdapter);
504            final int checkedItem = mCheckedItem;
505            if (checkedItem > -1) {
506                listView.setItemChecked(checkedItem, true);
507                listView.setSelection(checkedItem);
508            }
509        }
510    }
511
512    private void setScrollIndicators(ViewGroup contentPanel, View content,
513            final int indicators, final int mask) {
514        // Set up scroll indicators (if present).
515        View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp);
516        View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown);
517
518        if (Build.VERSION.SDK_INT >= 23) {
519            // We're on Marshmallow so can rely on the View APIs
520            ViewCompat.setScrollIndicators(content, indicators, mask);
521            // We can also remove the compat indicator views
522            if (indicatorUp != null) {
523                contentPanel.removeView(indicatorUp);
524            }
525            if (indicatorDown != null) {
526                contentPanel.removeView(indicatorDown);
527            }
528        } else {
529            // First, remove the indicator views if we're not set to use them
530            if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) {
531                contentPanel.removeView(indicatorUp);
532                indicatorUp = null;
533            }
534            if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) {
535                contentPanel.removeView(indicatorDown);
536                indicatorDown = null;
537            }
538
539            if (indicatorUp != null || indicatorDown != null) {
540                final View top = indicatorUp;
541                final View bottom = indicatorDown;
542
543                if (mMessage != null) {
544                    // We're just showing the ScrollView, set up listener.
545                    mScrollView.setOnScrollChangeListener(
546                            new NestedScrollView.OnScrollChangeListener() {
547                                @Override
548                                public void onScrollChange(NestedScrollView v, int scrollX,
549                                        int scrollY,
550                                        int oldScrollX, int oldScrollY) {
551                                    manageScrollIndicators(v, top, bottom);
552                                }
553                            });
554                    // Set up the indicators following layout.
555                    mScrollView.post(new Runnable() {
556                        @Override
557                        public void run() {
558                            manageScrollIndicators(mScrollView, top, bottom);
559                        }
560                    });
561                } else if (mListView != null) {
562                    // We're just showing the AbsListView, set up listener.
563                    mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
564                        @Override
565                        public void onScrollStateChanged(AbsListView view, int scrollState) {}
566
567                        @Override
568                        public void onScroll(AbsListView v, int firstVisibleItem,
569                                int visibleItemCount, int totalItemCount) {
570                            manageScrollIndicators(v, top, bottom);
571                        }
572                    });
573                    // Set up the indicators following layout.
574                    mListView.post(new Runnable() {
575                        @Override
576                        public void run() {
577                            manageScrollIndicators(mListView, top, bottom);
578                        }
579                    });
580                } else {
581                    // We don't have any content to scroll, remove the indicators.
582                    if (top != null) {
583                        contentPanel.removeView(top);
584                    }
585                    if (bottom != null) {
586                        contentPanel.removeView(bottom);
587                    }
588                }
589            }
590        }
591    }
592
593    private void setupCustomContent(ViewGroup customPanel) {
594        final View customView;
595        if (mView != null) {
596            customView = mView;
597        } else if (mViewLayoutResId != 0) {
598            final LayoutInflater inflater = LayoutInflater.from(mContext);
599            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
600        } else {
601            customView = null;
602        }
603
604        final boolean hasCustomView = customView != null;
605        if (!hasCustomView || !canTextInput(customView)) {
606            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
607                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
608        }
609
610        if (hasCustomView) {
611            final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
612            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
613
614            if (mViewSpacingSpecified) {
615                custom.setPadding(
616                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
617            }
618
619            if (mListView != null) {
620                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
621            }
622        } else {
623            customPanel.setVisibility(View.GONE);
624        }
625    }
626
627    private void setupTitle(ViewGroup topPanel) {
628        if (mCustomTitleView != null) {
629            // Add the custom title view directly to the topPanel layout
630            LayoutParams lp = new LayoutParams(
631                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
632
633            topPanel.addView(mCustomTitleView, 0, lp);
634
635            // Hide the title template
636            View titleTemplate = mWindow.findViewById(R.id.title_template);
637            titleTemplate.setVisibility(View.GONE);
638        } else {
639            mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);
640
641            final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
642            if (hasTextTitle) {
643                // Display the title if a title is supplied, else hide it.
644                mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
645                mTitleView.setText(mTitle);
646
647                // Do this last so that if the user has supplied any icons we
648                // use them instead of the default ones. If the user has
649                // specified 0 then make it disappear.
650                if (mIconId != 0) {
651                    mIconView.setImageResource(mIconId);
652                } else if (mIcon != null) {
653                    mIconView.setImageDrawable(mIcon);
654                } else {
655                    // Apply the padding from the icon to ensure the title is
656                    // aligned correctly.
657                    mTitleView.setPadding(mIconView.getPaddingLeft(),
658                            mIconView.getPaddingTop(),
659                            mIconView.getPaddingRight(),
660                            mIconView.getPaddingBottom());
661                    mIconView.setVisibility(View.GONE);
662                }
663            } else {
664                // Hide the title template
665                final View titleTemplate = mWindow.findViewById(R.id.title_template);
666                titleTemplate.setVisibility(View.GONE);
667                mIconView.setVisibility(View.GONE);
668                topPanel.setVisibility(View.GONE);
669            }
670        }
671    }
672
673    private void setupContent(ViewGroup contentPanel) {
674        mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView);
675        mScrollView.setFocusable(false);
676        mScrollView.setNestedScrollingEnabled(false);
677
678        // Special case for users that only want to display a String
679        mMessageView = (TextView) contentPanel.findViewById(android.R.id.message);
680        if (mMessageView == null) {
681            return;
682        }
683
684        if (mMessage != null) {
685            mMessageView.setText(mMessage);
686        } else {
687            mMessageView.setVisibility(View.GONE);
688            mScrollView.removeView(mMessageView);
689
690            if (mListView != null) {
691                final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
692                final int childIndex = scrollParent.indexOfChild(mScrollView);
693                scrollParent.removeViewAt(childIndex);
694                scrollParent.addView(mListView, childIndex,
695                        new LayoutParams(MATCH_PARENT, MATCH_PARENT));
696            } else {
697                contentPanel.setVisibility(View.GONE);
698            }
699        }
700    }
701
702    private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
703        if (upIndicator != null) {
704            upIndicator.setVisibility(
705                    ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE);
706        }
707        if (downIndicator != null) {
708            downIndicator.setVisibility(
709                    ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE);
710        }
711    }
712
713    private void setupButtons(ViewGroup buttonPanel) {
714        int BIT_BUTTON_POSITIVE = 1;
715        int BIT_BUTTON_NEGATIVE = 2;
716        int BIT_BUTTON_NEUTRAL = 4;
717        int whichButtons = 0;
718        mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
719        mButtonPositive.setOnClickListener(mButtonHandler);
720
721        if (TextUtils.isEmpty(mButtonPositiveText)) {
722            mButtonPositive.setVisibility(View.GONE);
723        } else {
724            mButtonPositive.setText(mButtonPositiveText);
725            mButtonPositive.setVisibility(View.VISIBLE);
726            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
727        }
728
729        mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);
730        mButtonNegative.setOnClickListener(mButtonHandler);
731
732        if (TextUtils.isEmpty(mButtonNegativeText)) {
733            mButtonNegative.setVisibility(View.GONE);
734        } else {
735            mButtonNegative.setText(mButtonNegativeText);
736            mButtonNegative.setVisibility(View.VISIBLE);
737
738            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
739        }
740
741        mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3);
742        mButtonNeutral.setOnClickListener(mButtonHandler);
743
744        if (TextUtils.isEmpty(mButtonNeutralText)) {
745            mButtonNeutral.setVisibility(View.GONE);
746        } else {
747            mButtonNeutral.setText(mButtonNeutralText);
748            mButtonNeutral.setVisibility(View.VISIBLE);
749
750            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
751        }
752
753        final boolean hasButtons = whichButtons != 0;
754        if (!hasButtons) {
755            buttonPanel.setVisibility(View.GONE);
756        }
757    }
758
759    public static class AlertParams {
760        public final Context mContext;
761        public final LayoutInflater mInflater;
762
763        public int mIconId = 0;
764        public Drawable mIcon;
765        public int mIconAttrId = 0;
766        public CharSequence mTitle;
767        public View mCustomTitleView;
768        public CharSequence mMessage;
769        public CharSequence mPositiveButtonText;
770        public DialogInterface.OnClickListener mPositiveButtonListener;
771        public CharSequence mNegativeButtonText;
772        public DialogInterface.OnClickListener mNegativeButtonListener;
773        public CharSequence mNeutralButtonText;
774        public DialogInterface.OnClickListener mNeutralButtonListener;
775        public boolean mCancelable;
776        public DialogInterface.OnCancelListener mOnCancelListener;
777        public DialogInterface.OnDismissListener mOnDismissListener;
778        public DialogInterface.OnKeyListener mOnKeyListener;
779        public CharSequence[] mItems;
780        public ListAdapter mAdapter;
781        public DialogInterface.OnClickListener mOnClickListener;
782        public int mViewLayoutResId;
783        public View mView;
784        public int mViewSpacingLeft;
785        public int mViewSpacingTop;
786        public int mViewSpacingRight;
787        public int mViewSpacingBottom;
788        public boolean mViewSpacingSpecified = false;
789        public boolean[] mCheckedItems;
790        public boolean mIsMultiChoice;
791        public boolean mIsSingleChoice;
792        public int mCheckedItem = -1;
793        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
794        public Cursor mCursor;
795        public String mLabelColumn;
796        public String mIsCheckedColumn;
797        public boolean mForceInverseBackground;
798        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
799        public OnPrepareListViewListener mOnPrepareListViewListener;
800        public boolean mRecycleOnMeasure = true;
801
802        /**
803         * Interface definition for a callback to be invoked before the ListView
804         * will be bound to an adapter.
805         */
806        public interface OnPrepareListViewListener {
807
808            /**
809             * Called before the ListView is bound to an adapter.
810             * @param listView The ListView that will be shown in the dialog.
811             */
812            void onPrepareListView(ListView listView);
813        }
814
815        public AlertParams(Context context) {
816            mContext = context;
817            mCancelable = true;
818            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
819        }
820
821        public void apply(AlertController dialog) {
822            if (mCustomTitleView != null) {
823                dialog.setCustomTitle(mCustomTitleView);
824            } else {
825                if (mTitle != null) {
826                    dialog.setTitle(mTitle);
827                }
828                if (mIcon != null) {
829                    dialog.setIcon(mIcon);
830                }
831                if (mIconId != 0) {
832                    dialog.setIcon(mIconId);
833                }
834                if (mIconAttrId != 0) {
835                    dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
836                }
837            }
838            if (mMessage != null) {
839                dialog.setMessage(mMessage);
840            }
841            if (mPositiveButtonText != null) {
842                dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
843                        mPositiveButtonListener, null);
844            }
845            if (mNegativeButtonText != null) {
846                dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
847                        mNegativeButtonListener, null);
848            }
849            if (mNeutralButtonText != null) {
850                dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
851                        mNeutralButtonListener, null);
852            }
853            // For a list, the client can either supply an array of items or an
854            // adapter or a cursor
855            if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
856                createListView(dialog);
857            }
858            if (mView != null) {
859                if (mViewSpacingSpecified) {
860                    dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
861                            mViewSpacingBottom);
862                } else {
863                    dialog.setView(mView);
864                }
865            } else if (mViewLayoutResId != 0) {
866                dialog.setView(mViewLayoutResId);
867            }
868
869            /*
870            dialog.setCancelable(mCancelable);
871            dialog.setOnCancelListener(mOnCancelListener);
872            if (mOnKeyListener != null) {
873                dialog.setOnKeyListener(mOnKeyListener);
874            }
875            */
876        }
877
878        private void createListView(final AlertController dialog) {
879            final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null);
880            final ListAdapter adapter;
881
882            if (mIsMultiChoice) {
883                if (mCursor == null) {
884                    adapter = new ArrayAdapter<CharSequence>(
885                            mContext, dialog.mMultiChoiceItemLayout, android.R.id.text1, mItems) {
886                        @Override
887                        public View getView(int position, View convertView, ViewGroup parent) {
888                            View view = super.getView(position, convertView, parent);
889                            if (mCheckedItems != null) {
890                                boolean isItemChecked = mCheckedItems[position];
891                                if (isItemChecked) {
892                                    listView.setItemChecked(position, true);
893                                }
894                            }
895                            return view;
896                        }
897                    };
898                } else {
899                    adapter = new CursorAdapter(mContext, mCursor, false) {
900                        private final int mLabelIndex;
901                        private final int mIsCheckedIndex;
902
903                        {
904                            final Cursor cursor = getCursor();
905                            mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
906                            mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
907                        }
908
909                        @Override
910                        public void bindView(View view, Context context, Cursor cursor) {
911                            CheckedTextView text = (CheckedTextView) view.findViewById(
912                                    android.R.id.text1);
913                            text.setText(cursor.getString(mLabelIndex));
914                            listView.setItemChecked(cursor.getPosition(),
915                                    cursor.getInt(mIsCheckedIndex) == 1);
916                        }
917
918                        @Override
919                        public View newView(Context context, Cursor cursor, ViewGroup parent) {
920                            return mInflater.inflate(dialog.mMultiChoiceItemLayout,
921                                    parent, false);
922                        }
923
924                    };
925                }
926            } else {
927                final int layout;
928                if (mIsSingleChoice) {
929                    layout = dialog.mSingleChoiceItemLayout;
930                } else {
931                    layout = dialog.mListItemLayout;
932                }
933
934                if (mCursor != null) {
935                    adapter = new SimpleCursorAdapter(mContext, layout, mCursor,
936                            new String[] { mLabelColumn }, new int[] { android.R.id.text1 });
937                } else if (mAdapter != null) {
938                    adapter = mAdapter;
939                } else {
940                    adapter = new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems);
941                }
942            }
943
944            if (mOnPrepareListViewListener != null) {
945                mOnPrepareListViewListener.onPrepareListView(listView);
946            }
947
948            /* Don't directly set the adapter on the ListView as we might
949             * want to add a footer to the ListView later.
950             */
951            dialog.mAdapter = adapter;
952            dialog.mCheckedItem = mCheckedItem;
953
954            if (mOnClickListener != null) {
955                listView.setOnItemClickListener(new OnItemClickListener() {
956                    @Override
957                    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
958                        mOnClickListener.onClick(dialog.mDialog, position);
959                        if (!mIsSingleChoice) {
960                            dialog.mDialog.dismiss();
961                        }
962                    }
963                });
964            } else if (mOnCheckboxClickListener != null) {
965                listView.setOnItemClickListener(new OnItemClickListener() {
966                    @Override
967                    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
968                        if (mCheckedItems != null) {
969                            mCheckedItems[position] = listView.isItemChecked(position);
970                        }
971                        mOnCheckboxClickListener.onClick(
972                                dialog.mDialog, position, listView.isItemChecked(position));
973                    }
974                });
975            }
976
977            // Attach a given OnItemSelectedListener to the ListView
978            if (mOnItemSelectedListener != null) {
979                listView.setOnItemSelectedListener(mOnItemSelectedListener);
980            }
981
982            if (mIsSingleChoice) {
983                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
984            } else if (mIsMultiChoice) {
985                listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
986            }
987            dialog.mListView = listView;
988        }
989    }
990
991    private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> {
992        public CheckedItemAdapter(Context context, int resource, int textViewResourceId,
993                CharSequence[] objects) {
994            super(context, resource, textViewResourceId, objects);
995        }
996
997        @Override
998        public boolean hasStableIds() {
999            return true;
1000        }
1001
1002        @Override
1003        public long getItemId(int position) {
1004            return position;
1005        }
1006    }
1007}
1008