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