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