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