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.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 
    151         public ButtonHandler(DialogInterface dialog) {
    152             mDialog = new WeakReference<DialogInterface>(dialog);
    153         }
    154 
    155         @Override
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    261     public void setCustomTitle(View customTitleView) {
    262         mCustomTitleView = customTitleView;
    263     }
    264 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    395     public int getIconAttributeResId(int attrId) {
    396         TypedValue out = new TypedValue();
    397         mContext.getTheme().resolveAttribute(attrId, out, true);
    398         return out.resourceId;
    399     }
    400 
    401     public void setInverseBackgroundForced(boolean forceInverseBackground) {
    402         mForceInverseBackground = forceInverseBackground;
    403     }
    404 
    405     public ListView getListView() {
    406         return mListView;
    407     }
    408 
    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"})
    423     public boolean onKeyDown(int keyCode, KeyEvent event) {
    424         return mScrollView != null && mScrollView.executeKeyEvent(event);
    425     }
    426 
    427     @SuppressWarnings({"UnusedDeclaration"})
    428     public boolean onKeyUp(int keyCode, KeyEvent event) {
    429         return mScrollView != null && mScrollView.executeKeyEvent(event);
    430     }
    431 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    863         public RecycleListView(Context context) {
    864             super(context);
    865         }
    866 
    867         public RecycleListView(Context context, AttributeSet attrs) {
    868             super(context, attrs);
    869         }
    870 
    871         public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) {
    872             super(context, attrs, defStyleAttr);
    873         }
    874 
    875         public RecycleListView(
    876                 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    877             super(context, attrs, defStyleAttr, defStyleRes);
    878         }
    879 
    880         @Override
    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              */
    939             void onPrepareListView(ListView listView);
    940         }
    941 
    942         public AlertParams(Context context) {
    943             mContext = context;
    944             mCancelable = true;
    945             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    946         }
    947 
    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 
   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> {
   1117         public CheckedItemAdapter(Context context, int resource, int textViewResourceId,
   1118                 CharSequence[] objects) {
   1119             super(context, resource, textViewResourceId, objects);
   1120         }
   1121 
   1122         @Override
   1123         public boolean hasStableIds() {
   1124             return true;
   1125         }
   1126 
   1127         @Override
   1128         public long getItemId(int position) {
   1129             return position;
   1130         }
   1131     }
   1132 }
   1133