OSDN Git Service

Add offset to round screens when an AlertDialog list item is shown.
[android-x86/frameworks-base.git] / core / java / com / android / internal / app / AlertController.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.internal.app;
18
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20
21 import com.android.internal.R;
22
23 import android.annotation.Nullable;
24 import android.app.AlertDialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.res.Configuration;
28 import android.content.res.TypedArray;
29 import android.database.Cursor;
30 import android.graphics.drawable.Drawable;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.text.TextUtils;
34 import android.util.AttributeSet;
35 import android.util.TypedValue;
36 import android.view.Gravity;
37 import android.view.KeyEvent;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.ViewGroup;
41 import android.view.ViewGroup.LayoutParams;
42 import android.view.ViewParent;
43 import android.view.ViewStub;
44 import android.view.Window;
45 import android.view.WindowInsets;
46 import android.view.WindowManager;
47 import android.widget.AdapterView;
48 import android.widget.AdapterView.OnItemClickListener;
49 import android.widget.ArrayAdapter;
50 import android.widget.Button;
51 import android.widget.CheckedTextView;
52 import android.widget.CursorAdapter;
53 import android.widget.FrameLayout;
54 import android.widget.ImageView;
55 import android.widget.LinearLayout;
56 import android.widget.ListAdapter;
57 import android.widget.ListView;
58 import android.widget.ScrollView;
59 import android.widget.SimpleCursorAdapter;
60 import android.widget.TextView;
61
62 import java.lang.ref.WeakReference;
63
64 public class AlertController {
65     public static final int MICRO = 1;
66
67     private final Context mContext;
68     private final DialogInterface mDialogInterface;
69     protected final Window mWindow;
70
71     private CharSequence mTitle;
72     protected CharSequence mMessage;
73     protected ListView mListView;
74     private View mView;
75
76     private int mViewLayoutResId;
77
78     private int mViewSpacingLeft;
79     private int mViewSpacingTop;
80     private int mViewSpacingRight;
81     private int mViewSpacingBottom;
82     private boolean mViewSpacingSpecified = false;
83
84     private Button mButtonPositive;
85     private CharSequence mButtonPositiveText;
86     private Message mButtonPositiveMessage;
87
88     private Button mButtonNegative;
89     private CharSequence mButtonNegativeText;
90     private Message mButtonNegativeMessage;
91
92     private Button mButtonNeutral;
93     private CharSequence mButtonNeutralText;
94     private Message mButtonNeutralMessage;
95
96     protected ScrollView mScrollView;
97
98     private int mIconId = 0;
99     private Drawable mIcon;
100
101     private ImageView mIconView;
102     private TextView mTitleView;
103     protected TextView mMessageView;
104     private View mCustomTitleView;
105
106     private boolean mForceInverseBackground;
107
108     private ListAdapter mAdapter;
109
110     private int mCheckedItem = -1;
111
112     private int mAlertDialogLayout;
113     private int mButtonPanelSideLayout;
114     private int mListLayout;
115     private int mMultiChoiceItemLayout;
116     private int mSingleChoiceItemLayout;
117     private int mListItemLayout;
118
119     private boolean mShowTitle;
120
121     private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
122
123     private Handler mHandler;
124
125     private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
126         @Override
127         public void onClick(View v) {
128             final Message m;
129             if (v == mButtonPositive && mButtonPositiveMessage != null) {
130                 m = Message.obtain(mButtonPositiveMessage);
131             } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
132                 m = Message.obtain(mButtonNegativeMessage);
133             } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
134                 m = Message.obtain(mButtonNeutralMessage);
135             } else {
136                 m = null;
137             }
138
139             if (m != null) {
140                 m.sendToTarget();
141             }
142
143             // Post a message so we dismiss after the above handlers are executed
144             mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
145                     .sendToTarget();
146         }
147     };
148
149     private static final class ButtonHandler extends Handler {
150         // Button clicks have Message.what as the BUTTON{1,2,3} constant
151         private static final int MSG_DISMISS_DIALOG = 1;
152
153         private WeakReference<DialogInterface> mDialog;
154
155         public ButtonHandler(DialogInterface dialog) {
156             mDialog = new WeakReference<>(dialog);
157         }
158
159         @Override
160         public void handleMessage(Message msg) {
161             switch (msg.what) {
162
163                 case DialogInterface.BUTTON_POSITIVE:
164                 case DialogInterface.BUTTON_NEGATIVE:
165                 case DialogInterface.BUTTON_NEUTRAL:
166                     ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
167                     break;
168
169                 case MSG_DISMISS_DIALOG:
170                     ((DialogInterface) msg.obj).dismiss();
171             }
172         }
173     }
174
175     private static boolean shouldCenterSingleButton(Context context) {
176         final TypedValue outValue = new TypedValue();
177         context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, outValue, true);
178         return outValue.data != 0;
179     }
180
181     public static final AlertController create(Context context, DialogInterface di, Window window) {
182         final TypedArray a = context.obtainStyledAttributes(
183                 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
184         int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0);
185         a.recycle();
186
187         switch (controllerType) {
188             case MICRO:
189                 return new MicroAlertController(context, di, window);
190             default:
191                 return new AlertController(context, di, window);
192         }
193     }
194
195     protected AlertController(Context context, DialogInterface di, Window window) {
196         mContext = context;
197         mDialogInterface = di;
198         mWindow = window;
199         mHandler = new ButtonHandler(di);
200
201         final TypedArray a = context.obtainStyledAttributes(null,
202                 R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
203
204         mAlertDialogLayout = a.getResourceId(
205                 R.styleable.AlertDialog_layout, R.layout.alert_dialog);
206         mButtonPanelSideLayout = a.getResourceId(
207                 R.styleable.AlertDialog_buttonPanelSideLayout, 0);
208         mListLayout = a.getResourceId(
209                 R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
210
211         mMultiChoiceItemLayout = a.getResourceId(
212                 R.styleable.AlertDialog_multiChoiceItemLayout,
213                 R.layout.select_dialog_multichoice);
214         mSingleChoiceItemLayout = a.getResourceId(
215                 R.styleable.AlertDialog_singleChoiceItemLayout,
216                 R.layout.select_dialog_singlechoice);
217         mListItemLayout = a.getResourceId(
218                 R.styleable.AlertDialog_listItemLayout,
219                 R.layout.select_dialog_item);
220         mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
221
222         a.recycle();
223
224         /* We use a custom title so never request a window title */
225         window.requestFeature(Window.FEATURE_NO_TITLE);
226     }
227
228     static boolean canTextInput(View v) {
229         if (v.onCheckIsTextEditor()) {
230             return true;
231         }
232
233         if (!(v instanceof ViewGroup)) {
234             return false;
235         }
236
237         ViewGroup vg = (ViewGroup)v;
238         int i = vg.getChildCount();
239         while (i > 0) {
240             i--;
241             v = vg.getChildAt(i);
242             if (canTextInput(v)) {
243                 return true;
244             }
245         }
246
247         return false;
248     }
249
250     public void installContent() {
251         int contentView = selectContentView();
252         mWindow.setContentView(contentView);
253         setupView();
254     }
255
256     private int selectContentView() {
257         if (mButtonPanelSideLayout == 0) {
258             return mAlertDialogLayout;
259         }
260         if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
261             return mButtonPanelSideLayout;
262         }
263         // TODO: use layout hint side for long messages/lists
264         return mAlertDialogLayout;
265     }
266
267     public void setTitle(CharSequence title) {
268         mTitle = title;
269         if (mTitleView != null) {
270             mTitleView.setText(title);
271         }
272     }
273
274     /**
275      * @see AlertDialog.Builder#setCustomTitle(View)
276      */
277     public void setCustomTitle(View customTitleView) {
278         mCustomTitleView = customTitleView;
279     }
280
281     public void setMessage(CharSequence message) {
282         mMessage = message;
283         if (mMessageView != null) {
284             mMessageView.setText(message);
285         }
286     }
287
288     /**
289      * Set the view resource to display in the dialog.
290      */
291     public void setView(int layoutResId) {
292         mView = null;
293         mViewLayoutResId = layoutResId;
294         mViewSpacingSpecified = false;
295     }
296
297     /**
298      * Set the view to display in the dialog.
299      */
300     public void setView(View view) {
301         mView = view;
302         mViewLayoutResId = 0;
303         mViewSpacingSpecified = false;
304     }
305
306     /**
307      * Set the view to display in the dialog along with the spacing around that view
308      */
309     public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
310             int viewSpacingBottom) {
311         mView = view;
312         mViewLayoutResId = 0;
313         mViewSpacingSpecified = true;
314         mViewSpacingLeft = viewSpacingLeft;
315         mViewSpacingTop = viewSpacingTop;
316         mViewSpacingRight = viewSpacingRight;
317         mViewSpacingBottom = viewSpacingBottom;
318     }
319
320     /**
321      * Sets a hint for the best button panel layout.
322      */
323     public void setButtonPanelLayoutHint(int layoutHint) {
324         mButtonPanelLayoutHint = layoutHint;
325     }
326
327     /**
328      * Sets a click listener or a message to be sent when the button is clicked.
329      * You only need to pass one of {@code listener} or {@code msg}.
330      *
331      * @param whichButton Which button, can be one of
332      *            {@link DialogInterface#BUTTON_POSITIVE},
333      *            {@link DialogInterface#BUTTON_NEGATIVE}, or
334      *            {@link DialogInterface#BUTTON_NEUTRAL}
335      * @param text The text to display in positive button.
336      * @param listener The {@link DialogInterface.OnClickListener} to use.
337      * @param msg The {@link Message} to be sent when clicked.
338      */
339     public void setButton(int whichButton, CharSequence text,
340             DialogInterface.OnClickListener listener, Message msg) {
341
342         if (msg == null && listener != null) {
343             msg = mHandler.obtainMessage(whichButton, listener);
344         }
345
346         switch (whichButton) {
347
348             case DialogInterface.BUTTON_POSITIVE:
349                 mButtonPositiveText = text;
350                 mButtonPositiveMessage = msg;
351                 break;
352
353             case DialogInterface.BUTTON_NEGATIVE:
354                 mButtonNegativeText = text;
355                 mButtonNegativeMessage = msg;
356                 break;
357
358             case DialogInterface.BUTTON_NEUTRAL:
359                 mButtonNeutralText = text;
360                 mButtonNeutralMessage = msg;
361                 break;
362
363             default:
364                 throw new IllegalArgumentException("Button does not exist");
365         }
366     }
367
368     /**
369      * Specifies the icon to display next to the alert title.
370      *
371      * @param resId the resource identifier of the drawable to use as the icon,
372      *            or 0 for no icon
373      */
374     public void setIcon(int resId) {
375         mIcon = null;
376         mIconId = resId;
377
378         if (mIconView != null) {
379             if (resId != 0) {
380                 mIconView.setVisibility(View.VISIBLE);
381                 mIconView.setImageResource(mIconId);
382             } else {
383                 mIconView.setVisibility(View.GONE);
384             }
385         }
386     }
387
388     /**
389      * Specifies the icon to display next to the alert title.
390      *
391      * @param icon the drawable to use as the icon or null for no icon
392      */
393     public void setIcon(Drawable icon) {
394         mIcon = icon;
395         mIconId = 0;
396
397         if (mIconView != null) {
398             if (icon != null) {
399                 mIconView.setVisibility(View.VISIBLE);
400                 mIconView.setImageDrawable(icon);
401             } else {
402                 mIconView.setVisibility(View.GONE);
403             }
404         }
405     }
406
407     /**
408      * @param attrId the attributeId of the theme-specific drawable
409      * to resolve the resourceId for.
410      *
411      * @return resId the resourceId of the theme-specific drawable
412      */
413     public int getIconAttributeResId(int attrId) {
414         TypedValue out = new TypedValue();
415         mContext.getTheme().resolveAttribute(attrId, out, true);
416         return out.resourceId;
417     }
418
419     public void setInverseBackgroundForced(boolean forceInverseBackground) {
420         mForceInverseBackground = forceInverseBackground;
421     }
422
423     public ListView getListView() {
424         return mListView;
425     }
426
427     public Button getButton(int whichButton) {
428         switch (whichButton) {
429             case DialogInterface.BUTTON_POSITIVE:
430                 return mButtonPositive;
431             case DialogInterface.BUTTON_NEGATIVE:
432                 return mButtonNegative;
433             case DialogInterface.BUTTON_NEUTRAL:
434                 return mButtonNeutral;
435             default:
436                 return null;
437         }
438     }
439
440     @SuppressWarnings({"UnusedDeclaration"})
441     public boolean onKeyDown(int keyCode, KeyEvent event) {
442         return mScrollView != null && mScrollView.executeKeyEvent(event);
443     }
444
445     @SuppressWarnings({"UnusedDeclaration"})
446     public boolean onKeyUp(int keyCode, KeyEvent event) {
447         return mScrollView != null && mScrollView.executeKeyEvent(event);
448     }
449
450     /**
451      * Resolves whether a custom or default panel should be used. Removes the
452      * default panel if a custom panel should be used. If the resolved panel is
453      * a view stub, inflates before returning.
454      *
455      * @param customPanel the custom panel
456      * @param defaultPanel the default panel
457      * @return the panel to use
458      */
459     @Nullable
460     private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
461         if (customPanel == null) {
462             // Inflate the default panel, if needed.
463             if (defaultPanel instanceof ViewStub) {
464                 defaultPanel = ((ViewStub) defaultPanel).inflate();
465             }
466
467             return (ViewGroup) defaultPanel;
468         }
469
470         // Remove the default panel entirely.
471         if (defaultPanel != null) {
472             final ViewParent parent = defaultPanel.getParent();
473             if (parent instanceof ViewGroup) {
474                 ((ViewGroup) parent).removeView(defaultPanel);
475             }
476         }
477
478         // Inflate the custom panel, if needed.
479         if (customPanel instanceof ViewStub) {
480             customPanel = ((ViewStub) customPanel).inflate();
481         }
482
483         return (ViewGroup) customPanel;
484     }
485
486     private void setupView() {
487         final View parentPanel = mWindow.findViewById(R.id.parentPanel);
488         final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
489         final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
490         final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
491
492         // Install custom content before setting up the title or buttons so
493         // that we can handle panel overrides.
494         final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
495         setupCustomContent(customPanel);
496
497         final View customTopPanel = customPanel.findViewById(R.id.topPanel);
498         final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
499         final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
500
501         // Resolve the correct panels and remove the defaults, if needed.
502         final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
503         final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
504         final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
505
506         setupContent(contentPanel);
507         setupButtons(buttonPanel);
508         setupTitle(topPanel);
509
510         final boolean hasCustomPanel = customPanel != null
511                 && customPanel.getVisibility() != View.GONE;
512         final boolean hasTopPanel = topPanel != null
513                 && topPanel.getVisibility() != View.GONE;
514         final boolean hasButtonPanel = buttonPanel != null
515                 && buttonPanel.getVisibility() != View.GONE;
516
517         // Only display the text spacer if we don't have buttons.
518         if (!hasButtonPanel) {
519             if (contentPanel != null) {
520                 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
521                 if (spacer != null) {
522                     spacer.setVisibility(View.VISIBLE);
523                 }
524             }
525             mWindow.setCloseOnTouchOutsideIfNotSet(true);
526         }
527
528         if (hasTopPanel) {
529             // Only clip scrolling content to padding if we have a title.
530             if (mScrollView != null) {
531                 mScrollView.setClipToPadding(true);
532             }
533
534             // Only show the divider if we have a title.
535             View divider = null;
536             if (mMessage != null || mListView != null || hasCustomPanel) {
537                 if (!hasCustomPanel) {
538                     divider = topPanel.findViewById(R.id.titleDividerNoCustom);
539                 }
540                 if (divider == null) {
541                     divider = topPanel.findViewById(R.id.titleDivider);
542                 }
543
544             } else {
545                 divider = topPanel.findViewById(R.id.titleDividerTop);
546             }
547
548             if (divider != null) {
549                 divider.setVisibility(View.VISIBLE);
550             }
551         } else {
552             if (contentPanel != null) {
553                 final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
554                 if (spacer != null) {
555                     spacer.setVisibility(View.VISIBLE);
556                 }
557             }
558         }
559
560         if (mListView instanceof RecycleListView) {
561             ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);
562         }
563
564         // Update scroll indicators as needed.
565         if (!hasCustomPanel) {
566             final View content = mListView != null ? mListView : mScrollView;
567             if (content != null) {
568                 final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
569                         | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
570                 content.setScrollIndicators(indicators,
571                         View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
572             }
573         }
574
575         final TypedArray a = mContext.obtainStyledAttributes(
576                 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
577         setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
578                 hasTopPanel, hasCustomPanel, hasButtonPanel);
579         a.recycle();
580     }
581
582     private void setupCustomContent(ViewGroup customPanel) {
583         final View customView;
584         if (mView != null) {
585             customView = mView;
586         } else if (mViewLayoutResId != 0) {
587             final LayoutInflater inflater = LayoutInflater.from(mContext);
588             customView = inflater.inflate(mViewLayoutResId, customPanel, false);
589         } else {
590             customView = null;
591         }
592
593         final boolean hasCustomView = customView != null;
594         if (!hasCustomView || !canTextInput(customView)) {
595             mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
596                     WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
597         }
598
599         if (hasCustomView) {
600             final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
601             custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
602
603             if (mViewSpacingSpecified) {
604                 custom.setPadding(
605                         mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
606             }
607
608             if (mListView != null) {
609                 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
610             }
611         } else {
612             customPanel.setVisibility(View.GONE);
613         }
614     }
615
616     protected void setupTitle(ViewGroup topPanel) {
617         if (mCustomTitleView != null && mShowTitle) {
618             // Add the custom title view directly to the topPanel layout
619             final LayoutParams lp = new LayoutParams(
620                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
621
622             topPanel.addView(mCustomTitleView, 0, lp);
623
624             // Hide the title template
625             final View titleTemplate = mWindow.findViewById(R.id.title_template);
626             titleTemplate.setVisibility(View.GONE);
627         } else {
628             mIconView = (ImageView) mWindow.findViewById(R.id.icon);
629
630             final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
631             if (hasTextTitle && mShowTitle) {
632                 // Display the title if a title is supplied, else hide it.
633                 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
634                 mTitleView.setText(mTitle);
635
636                 // Do this last so that if the user has supplied any icons we
637                 // use them instead of the default ones. If the user has
638                 // specified 0 then make it disappear.
639                 if (mIconId != 0) {
640                     mIconView.setImageResource(mIconId);
641                 } else if (mIcon != null) {
642                     mIconView.setImageDrawable(mIcon);
643                 } else {
644                     // Apply the padding from the icon to ensure the title is
645                     // aligned correctly.
646                     mTitleView.setPadding(mIconView.getPaddingLeft(),
647                             mIconView.getPaddingTop(),
648                             mIconView.getPaddingRight(),
649                             mIconView.getPaddingBottom());
650                     mIconView.setVisibility(View.GONE);
651                 }
652             } else {
653                 // Hide the title template
654                 final View titleTemplate = mWindow.findViewById(R.id.title_template);
655                 titleTemplate.setVisibility(View.GONE);
656                 mIconView.setVisibility(View.GONE);
657                 topPanel.setVisibility(View.GONE);
658             }
659         }
660     }
661
662     protected void setupContent(ViewGroup contentPanel) {
663         mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView);
664         mScrollView.setFocusable(false);
665
666         // Special case for users that only want to display a String
667         mMessageView = (TextView) contentPanel.findViewById(R.id.message);
668         if (mMessageView == null) {
669             return;
670         }
671
672         if (mMessage != null) {
673             mMessageView.setText(mMessage);
674         } else {
675             mMessageView.setVisibility(View.GONE);
676             mScrollView.removeView(mMessageView);
677
678             if (mListView != null) {
679                 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
680                 final int childIndex = scrollParent.indexOfChild(mScrollView);
681                 scrollParent.removeViewAt(childIndex);
682                 scrollParent.addView(mListView, childIndex,
683                         new LayoutParams(MATCH_PARENT, MATCH_PARENT));
684             } else {
685                 contentPanel.setVisibility(View.GONE);
686             }
687         }
688     }
689
690     private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
691         if (upIndicator != null) {
692             upIndicator.setVisibility(v.canScrollVertically(-1) ? View.VISIBLE : View.INVISIBLE);
693         }
694         if (downIndicator != null) {
695             downIndicator.setVisibility(v.canScrollVertically(1) ? View.VISIBLE : View.INVISIBLE);
696         }
697     }
698
699     protected void setupButtons(ViewGroup buttonPanel) {
700         int BIT_BUTTON_POSITIVE = 1;
701         int BIT_BUTTON_NEGATIVE = 2;
702         int BIT_BUTTON_NEUTRAL = 4;
703         int whichButtons = 0;
704         mButtonPositive = (Button) buttonPanel.findViewById(R.id.button1);
705         mButtonPositive.setOnClickListener(mButtonHandler);
706
707         if (TextUtils.isEmpty(mButtonPositiveText)) {
708             mButtonPositive.setVisibility(View.GONE);
709         } else {
710             mButtonPositive.setText(mButtonPositiveText);
711             mButtonPositive.setVisibility(View.VISIBLE);
712             whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
713         }
714
715         mButtonNegative = (Button) buttonPanel.findViewById(R.id.button2);
716         mButtonNegative.setOnClickListener(mButtonHandler);
717
718         if (TextUtils.isEmpty(mButtonNegativeText)) {
719             mButtonNegative.setVisibility(View.GONE);
720         } else {
721             mButtonNegative.setText(mButtonNegativeText);
722             mButtonNegative.setVisibility(View.VISIBLE);
723
724             whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
725         }
726
727         mButtonNeutral = (Button) buttonPanel.findViewById(R.id.button3);
728         mButtonNeutral.setOnClickListener(mButtonHandler);
729
730         if (TextUtils.isEmpty(mButtonNeutralText)) {
731             mButtonNeutral.setVisibility(View.GONE);
732         } else {
733             mButtonNeutral.setText(mButtonNeutralText);
734             mButtonNeutral.setVisibility(View.VISIBLE);
735
736             whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
737         }
738
739         if (shouldCenterSingleButton(mContext)) {
740             /*
741              * If we only have 1 button it should be centered on the layout and
742              * expand to fill 50% of the available space.
743              */
744             if (whichButtons == BIT_BUTTON_POSITIVE) {
745                 centerButton(mButtonPositive);
746             } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
747                 centerButton(mButtonNegative);
748             } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
749                 centerButton(mButtonNeutral);
750             }
751         }
752
753         final boolean hasButtons = whichButtons != 0;
754         if (!hasButtons) {
755             buttonPanel.setVisibility(View.GONE);
756         }
757     }
758
759     private void centerButton(Button button) {
760         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
761         params.gravity = Gravity.CENTER_HORIZONTAL;
762         params.weight = 0.5f;
763         button.setLayoutParams(params);
764         View leftSpacer = mWindow.findViewById(R.id.leftSpacer);
765         if (leftSpacer != null) {
766             leftSpacer.setVisibility(View.VISIBLE);
767         }
768         View rightSpacer = mWindow.findViewById(R.id.rightSpacer);
769         if (rightSpacer != null) {
770             rightSpacer.setVisibility(View.VISIBLE);
771         }
772     }
773
774     private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
775             View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
776         int fullDark = 0;
777         int topDark = 0;
778         int centerDark = 0;
779         int bottomDark = 0;
780         int fullBright = 0;
781         int topBright = 0;
782         int centerBright = 0;
783         int bottomBright = 0;
784         int bottomMedium = 0;
785
786         // If the needsDefaultBackgrounds attribute is set, we know we're
787         // inheriting from a framework style.
788         final boolean needsDefaultBackgrounds = a.getBoolean(
789                 R.styleable.AlertDialog_needsDefaultBackgrounds, true);
790         if (needsDefaultBackgrounds) {
791             fullDark = R.drawable.popup_full_dark;
792             topDark = R.drawable.popup_top_dark;
793             centerDark = R.drawable.popup_center_dark;
794             bottomDark = R.drawable.popup_bottom_dark;
795             fullBright = R.drawable.popup_full_bright;
796             topBright = R.drawable.popup_top_bright;
797             centerBright = R.drawable.popup_center_bright;
798             bottomBright = R.drawable.popup_bottom_bright;
799             bottomMedium = R.drawable.popup_bottom_medium;
800         }
801
802         topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
803         topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
804         centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
805         centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
806
807         /* We now set the background of all of the sections of the alert.
808          * First collect together each section that is being displayed along
809          * with whether it is on a light or dark background, then run through
810          * them setting their backgrounds.  This is complicated because we need
811          * to correctly use the full, top, middle, and bottom graphics depending
812          * on how many views they are and where they appear.
813          */
814
815         final View[] views = new View[4];
816         final boolean[] light = new boolean[4];
817         View lastView = null;
818         boolean lastLight = false;
819
820         int pos = 0;
821         if (hasTitle) {
822             views[pos] = topPanel;
823             light[pos] = false;
824             pos++;
825         }
826
827         /* The contentPanel displays either a custom text message or
828          * a ListView. If it's text we should use the dark background
829          * for ListView we should use the light background. If neither
830          * are there the contentPanel will be hidden so set it as null.
831          */
832         views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
833         light[pos] = mListView != null;
834         pos++;
835
836         if (hasCustomView) {
837             views[pos] = customPanel;
838             light[pos] = mForceInverseBackground;
839             pos++;
840         }
841
842         if (hasButtons) {
843             views[pos] = buttonPanel;
844             light[pos] = true;
845         }
846
847         boolean setView = false;
848         for (pos = 0; pos < views.length; pos++) {
849             final View v = views[pos];
850             if (v == null) {
851                 continue;
852             }
853
854             if (lastView != null) {
855                 if (!setView) {
856                     lastView.setBackgroundResource(lastLight ? topBright : topDark);
857                 } else {
858                     lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
859                 }
860                 setView = true;
861             }
862
863             lastView = v;
864             lastLight = light[pos];
865         }
866
867         if (lastView != null) {
868             if (setView) {
869                 bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
870                 bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
871                 bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
872
873                 // ListViews will use the Bright background, but buttons use the
874                 // Medium background.
875                 lastView.setBackgroundResource(
876                         lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
877             } else {
878                 fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
879                 fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
880
881                 lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
882             }
883         }
884
885         final ListView listView = mListView;
886         if (listView != null && mAdapter != null) {
887             listView.setAdapter(mAdapter);
888             final int checkedItem = mCheckedItem;
889             if (checkedItem > -1) {
890                 listView.setItemChecked(checkedItem, true);
891                 listView.setSelectionFromTop(checkedItem,
892                         a.getDimensionPixelSize(R.styleable.AlertDialog_selectionScrollOffset, 0));
893             }
894         }
895     }
896
897     public static class RecycleListView extends ListView {
898         private final int mPaddingTopNoTitle;
899         private final int mPaddingBottomNoButtons;
900
901         boolean mRecycleOnMeasure = true;
902
903         public RecycleListView(Context context) {
904             this(context, null);
905         }
906
907         public RecycleListView(Context context, AttributeSet attrs) {
908             super(context, attrs);
909
910             final TypedArray ta = context.obtainStyledAttributes(
911                     attrs, R.styleable.RecycleListView);
912             mPaddingBottomNoButtons = ta.getDimensionPixelOffset(
913                     R.styleable.RecycleListView_paddingBottomNoButtons, -1);
914             mPaddingTopNoTitle = ta.getDimensionPixelOffset(
915                     R.styleable.RecycleListView_paddingTopNoTitle, -1);
916         }
917
918         public void setHasDecor(boolean hasTitle, boolean hasButtons) {
919             if (!hasButtons || !hasTitle) {
920                 final int paddingLeft = getPaddingLeft();
921                 final int paddingTop = hasTitle ? getPaddingTop() : mPaddingTopNoTitle;
922                 final int paddingRight = getPaddingRight();
923                 final int paddingBottom = hasButtons ? getPaddingBottom() : mPaddingBottomNoButtons;
924                 setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
925             }
926         }
927
928         @Override
929         protected boolean recycleOnMeasure() {
930             return mRecycleOnMeasure;
931         }
932     }
933
934     public static class AlertParams {
935         public final Context mContext;
936         public final LayoutInflater mInflater;
937
938         public int mIconId = 0;
939         public Drawable mIcon;
940         public int mIconAttrId = 0;
941         public CharSequence mTitle;
942         public View mCustomTitleView;
943         public CharSequence mMessage;
944         public CharSequence mPositiveButtonText;
945         public DialogInterface.OnClickListener mPositiveButtonListener;
946         public CharSequence mNegativeButtonText;
947         public DialogInterface.OnClickListener mNegativeButtonListener;
948         public CharSequence mNeutralButtonText;
949         public DialogInterface.OnClickListener mNeutralButtonListener;
950         public boolean mCancelable;
951         public DialogInterface.OnCancelListener mOnCancelListener;
952         public DialogInterface.OnDismissListener mOnDismissListener;
953         public DialogInterface.OnKeyListener mOnKeyListener;
954         public CharSequence[] mItems;
955         public ListAdapter mAdapter;
956         public DialogInterface.OnClickListener mOnClickListener;
957         public int mViewLayoutResId;
958         public View mView;
959         public int mViewSpacingLeft;
960         public int mViewSpacingTop;
961         public int mViewSpacingRight;
962         public int mViewSpacingBottom;
963         public boolean mViewSpacingSpecified = false;
964         public boolean[] mCheckedItems;
965         public boolean mIsMultiChoice;
966         public boolean mIsSingleChoice;
967         public int mCheckedItem = -1;
968         public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
969         public Cursor mCursor;
970         public String mLabelColumn;
971         public String mIsCheckedColumn;
972         public boolean mForceInverseBackground;
973         public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
974         public OnPrepareListViewListener mOnPrepareListViewListener;
975         public boolean mRecycleOnMeasure = true;
976
977         /**
978          * Interface definition for a callback to be invoked before the ListView
979          * will be bound to an adapter.
980          */
981         public interface OnPrepareListViewListener {
982
983             /**
984              * Called before the ListView is bound to an adapter.
985              * @param listView The ListView that will be shown in the dialog.
986              */
987             void onPrepareListView(ListView listView);
988         }
989
990         public AlertParams(Context context) {
991             mContext = context;
992             mCancelable = true;
993             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
994         }
995
996         public void apply(AlertController dialog) {
997             if (mCustomTitleView != null) {
998                 dialog.setCustomTitle(mCustomTitleView);
999             } else {
1000                 if (mTitle != null) {
1001                     dialog.setTitle(mTitle);
1002                 }
1003                 if (mIcon != null) {
1004                     dialog.setIcon(mIcon);
1005                 }
1006                 if (mIconId != 0) {
1007                     dialog.setIcon(mIconId);
1008                 }
1009                 if (mIconAttrId != 0) {
1010                     dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
1011                 }
1012             }
1013             if (mMessage != null) {
1014                 dialog.setMessage(mMessage);
1015             }
1016             if (mPositiveButtonText != null) {
1017                 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
1018                         mPositiveButtonListener, null);
1019             }
1020             if (mNegativeButtonText != null) {
1021                 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
1022                         mNegativeButtonListener, null);
1023             }
1024             if (mNeutralButtonText != null) {
1025                 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
1026                         mNeutralButtonListener, null);
1027             }
1028             if (mForceInverseBackground) {
1029                 dialog.setInverseBackgroundForced(true);
1030             }
1031             // For a list, the client can either supply an array of items or an
1032             // adapter or a cursor
1033             if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
1034                 createListView(dialog);
1035             }
1036             if (mView != null) {
1037                 if (mViewSpacingSpecified) {
1038                     dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
1039                             mViewSpacingBottom);
1040                 } else {
1041                     dialog.setView(mView);
1042                 }
1043             } else if (mViewLayoutResId != 0) {
1044                 dialog.setView(mViewLayoutResId);
1045             }
1046
1047             /*
1048             dialog.setCancelable(mCancelable);
1049             dialog.setOnCancelListener(mOnCancelListener);
1050             if (mOnKeyListener != null) {
1051                 dialog.setOnKeyListener(mOnKeyListener);
1052             }
1053             */
1054         }
1055
1056         private void createListView(final AlertController dialog) {
1057             final RecycleListView listView =
1058                     (RecycleListView) mInflater.inflate(dialog.mListLayout, null);
1059             final ListAdapter adapter;
1060
1061             if (mIsMultiChoice) {
1062                 if (mCursor == null) {
1063                     adapter = new ArrayAdapter<CharSequence>(
1064                             mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) {
1065                         @Override
1066                         public View getView(int position, View convertView, ViewGroup parent) {
1067                             View view = super.getView(position, convertView, parent);
1068                             if (mCheckedItems != null) {
1069                                 boolean isItemChecked = mCheckedItems[position];
1070                                 if (isItemChecked) {
1071                                     listView.setItemChecked(position, true);
1072                                 }
1073                             }
1074                             return view;
1075                         }
1076                     };
1077                 } else {
1078                     adapter = new CursorAdapter(mContext, mCursor, false) {
1079                         private final int mLabelIndex;
1080                         private final int mIsCheckedIndex;
1081
1082                         {
1083                             final Cursor cursor = getCursor();
1084                             mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
1085                             mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
1086                         }
1087
1088                         @Override
1089                         public void bindView(View view, Context context, Cursor cursor) {
1090                             CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
1091                             text.setText(cursor.getString(mLabelIndex));
1092                             listView.setItemChecked(
1093                                     cursor.getPosition(),
1094                                     cursor.getInt(mIsCheckedIndex) == 1);
1095                         }
1096
1097                         @Override
1098                         public View newView(Context context, Cursor cursor, ViewGroup parent) {
1099                             return mInflater.inflate(dialog.mMultiChoiceItemLayout,
1100                                     parent, false);
1101                         }
1102
1103                     };
1104                 }
1105             } else {
1106                 final int layout;
1107                 if (mIsSingleChoice) {
1108                     layout = dialog.mSingleChoiceItemLayout;
1109                 } else {
1110                     layout = dialog.mListItemLayout;
1111                 }
1112
1113                 if (mCursor != null) {
1114                     adapter = new SimpleCursorAdapter(mContext, layout, mCursor,
1115                             new String[] { mLabelColumn }, new int[] { R.id.text1 });
1116                 } else if (mAdapter != null) {
1117                     adapter = mAdapter;
1118                 } else {
1119                     adapter = new CheckedItemAdapter(mContext, layout, R.id.text1, mItems);
1120                 }
1121             }
1122
1123             if (mOnPrepareListViewListener != null) {
1124                 mOnPrepareListViewListener.onPrepareListView(listView);
1125             }
1126
1127             /* Don't directly set the adapter on the ListView as we might
1128              * want to add a footer to the ListView later.
1129              */
1130             dialog.mAdapter = adapter;
1131             dialog.mCheckedItem = mCheckedItem;
1132
1133             if (mOnClickListener != null) {
1134                 listView.setOnItemClickListener(new OnItemClickListener() {
1135                     @Override
1136                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
1137                         mOnClickListener.onClick(dialog.mDialogInterface, position);
1138                         if (!mIsSingleChoice) {
1139                             dialog.mDialogInterface.dismiss();
1140                         }
1141                     }
1142                 });
1143             } else if (mOnCheckboxClickListener != null) {
1144                 listView.setOnItemClickListener(new OnItemClickListener() {
1145                     @Override
1146                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
1147                         if (mCheckedItems != null) {
1148                             mCheckedItems[position] = listView.isItemChecked(position);
1149                         }
1150                         mOnCheckboxClickListener.onClick(
1151                                 dialog.mDialogInterface, position, listView.isItemChecked(position));
1152                     }
1153                 });
1154             }
1155
1156             // Attach a given OnItemSelectedListener to the ListView
1157             if (mOnItemSelectedListener != null) {
1158                 listView.setOnItemSelectedListener(mOnItemSelectedListener);
1159             }
1160
1161             if (mIsSingleChoice) {
1162                 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
1163             } else if (mIsMultiChoice) {
1164                 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
1165             }
1166             listView.mRecycleOnMeasure = mRecycleOnMeasure;
1167             dialog.mListView = listView;
1168         }
1169     }
1170
1171     private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> {
1172         public CheckedItemAdapter(Context context, int resource, int textViewResourceId,
1173                 CharSequence[] objects) {
1174             super(context, resource, textViewResourceId, objects);
1175         }
1176
1177         @Override
1178         public boolean hasStableIds() {
1179             return true;
1180         }
1181
1182         @Override
1183         public long getItemId(int position) {
1184             return position;
1185         }
1186     }
1187 }