OSDN Git Service

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