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