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.annotation.Nullable;
     24 import android.app.AlertDialog;
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.content.res.TypedArray;
     28 import android.database.Cursor;
     29 import android.graphics.drawable.Drawable;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.text.TextUtils;
     33 import android.util.AttributeSet;
     34 import android.util.TypedValue;
     35 import android.view.Gravity;
     36 import android.view.KeyEvent;
     37 import android.view.LayoutInflater;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.view.ViewGroup.LayoutParams;
     41 import android.view.ViewParent;
     42 import android.view.ViewStub;
     43 import android.view.Window;
     44 import android.view.WindowInsets;
     45 import android.view.WindowManager;
     46 import android.widget.AbsListView;
     47 import android.widget.AdapterView;
     48 import android.widget.AdapterView.OnItemClickListener;
     49 import android.widget.ArrayAdapter;
     50 import android.widget.Button;
     51 import android.widget.CheckedTextView;
     52 import android.widget.CursorAdapter;
     53 import android.widget.FrameLayout;
     54 import android.widget.ImageView;
     55 import android.widget.LinearLayout;
     56 import android.widget.ListAdapter;
     57 import android.widget.ListView;
     58 import android.widget.ScrollView;
     59 import android.widget.SimpleCursorAdapter;
     60 import android.widget.TextView;
     61 
     62 import java.lang.ref.WeakReference;
     63 
     64 public class AlertController {
     65 
     66     private final Context mContext;
     67     private final DialogInterface mDialogInterface;
     68     private final Window mWindow;
     69 
     70     private CharSequence mTitle;
     71     private CharSequence mMessage;
     72     private ListView mListView;
     73     private View mView;
     74 
     75     private int mViewLayoutResId;
     76 
     77     private int mViewSpacingLeft;
     78     private int mViewSpacingTop;
     79     private int mViewSpacingRight;
     80     private int mViewSpacingBottom;
     81     private boolean mViewSpacingSpecified = false;
     82 
     83     private Button mButtonPositive;
     84     private CharSequence mButtonPositiveText;
     85     private Message mButtonPositiveMessage;
     86 
     87     private Button mButtonNegative;
     88     private CharSequence mButtonNegativeText;
     89     private Message mButtonNegativeMessage;
     90 
     91     private Button mButtonNeutral;
     92     private CharSequence mButtonNeutralText;
     93     private Message mButtonNeutralMessage;
     94 
     95     private ScrollView mScrollView;
     96 
     97     private int mIconId = 0;
     98     private Drawable mIcon;
     99 
    100     private ImageView mIconView;
    101     private TextView mTitleView;
    102     private TextView mMessageView;
    103     private View mCustomTitleView;
    104 
    105     private boolean mForceInverseBackground;
    106 
    107     private ListAdapter mAdapter;
    108 
    109     private int mCheckedItem = -1;
    110 
    111     private int mAlertDialogLayout;
    112     private int mButtonPanelSideLayout;
    113     private int mListLayout;
    114     private int mMultiChoiceItemLayout;
    115     private int mSingleChoiceItemLayout;
    116     private int mListItemLayout;
    117 
    118     private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
    119 
    120     private Handler mHandler;
    121 
    122     private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
    123         @Override
    124         public void onClick(View v) {
    125             final Message m;
    126             if (v == mButtonPositive && mButtonPositiveMessage != null) {
    127                 m = Message.obtain(mButtonPositiveMessage);
    128             } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
    129                 m = Message.obtain(mButtonNegativeMessage);
    130             } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
    131                 m = Message.obtain(mButtonNeutralMessage);
    132             } else {
    133                 m = null;
    134             }
    135 
    136             if (m != null) {
    137                 m.sendToTarget();
    138             }
    139 
    140             // Post a message so we dismiss after the above handlers are executed
    141             mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
    142                     .sendToTarget();
    143         }
    144     };
    145 
    146     private static final class ButtonHandler extends Handler {
    147         // Button clicks have Message.what as the BUTTON{1,2,3} constant
    148         private static final int MSG_DISMISS_DIALOG = 1;
    149 
    150         private WeakReference<DialogInterface> mDialog;
    151 
    152         public ButtonHandler(DialogInterface dialog) {
    153             mDialog = new WeakReference<DialogInterface>(dialog);
    154         }
    155 
    156         @Override
    157         public void handleMessage(Message msg) {
    158             switch (msg.what) {
    159 
    160                 case DialogInterface.BUTTON_POSITIVE:
    161                 case DialogInterface.BUTTON_NEGATIVE:
    162                 case DialogInterface.BUTTON_NEUTRAL:
    163                     ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
    164                     break;
    165 
    166                 case MSG_DISMISS_DIALOG:
    167                     ((DialogInterface) msg.obj).dismiss();
    168             }
    169         }
    170     }
    171 
    172     private static boolean shouldCenterSingleButton(Context context) {
    173         final TypedValue outValue = new TypedValue();
    174         context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, 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         final TypedArray a = context.obtainStyledAttributes(null,
    185                 R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    186 
    187         mAlertDialogLayout = a.getResourceId(
    188                 R.styleable.AlertDialog_layout, R.layout.alert_dialog);
    189         mButtonPanelSideLayout = a.getResourceId(
    190                 R.styleable.AlertDialog_buttonPanelSideLayout, 0);
    191         mListLayout = a.getResourceId(
    192                 R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
    193 
    194         mMultiChoiceItemLayout = a.getResourceId(
    195                 R.styleable.AlertDialog_multiChoiceItemLayout,
    196                 R.layout.select_dialog_multichoice);
    197         mSingleChoiceItemLayout = a.getResourceId(
    198                 R.styleable.AlertDialog_singleChoiceItemLayout,
    199                 R.layout.select_dialog_singlechoice);
    200         mListItemLayout = a.getResourceId(
    201                 R.styleable.AlertDialog_listItemLayout,
    202                 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     /**
    452      * Resolves whether a custom or default panel should be used. Removes the
    453      * default panel if a custom panel should be used. If the resolved panel is
    454      * a view stub, inflates before returning.
    455      *
    456      * @param customPanel the custom panel
    457      * @param defaultPanel the default panel
    458      * @return the panel to use
    459      */
    460     @Nullable
    461     private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
    462         if (customPanel == null) {
    463             // Inflate the default panel, if needed.
    464             if (defaultPanel instanceof ViewStub) {
    465                 defaultPanel = ((ViewStub) defaultPanel).inflate();
    466             }
    467 
    468             return (ViewGroup) defaultPanel;
    469         }
    470 
    471         // Remove the default panel entirely.
    472         if (defaultPanel != null) {
    473             final ViewParent parent = defaultPanel.getParent();
    474             if (parent instanceof ViewGroup) {
    475                 ((ViewGroup) parent).removeView(defaultPanel);
    476             }
    477         }
    478 
    479         // Inflate the custom panel, if needed.
    480         if (customPanel instanceof ViewStub) {
    481             customPanel = ((ViewStub) customPanel).inflate();
    482         }
    483 
    484         return (ViewGroup) customPanel;
    485     }
    486 
    487     private void setupView() {
    488         final View parentPanel = mWindow.findViewById(R.id.parentPanel);
    489         final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
    490         final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
    491         final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
    492 
    493         // Install custom content before setting up the title or buttons so
    494         // that we can handle panel overrides.
    495         final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
    496         setupCustomContent(customPanel);
    497 
    498         final View customTopPanel = customPanel.findViewById(R.id.topPanel);
    499         final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
    500         final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
    501 
    502         // Resolve the correct panels and remove the defaults, if needed.
    503         final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
    504         final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
    505         final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
    506 
    507         setupContent(contentPanel);
    508         setupButtons(buttonPanel);
    509         setupTitle(topPanel);
    510 
    511         final boolean hasCustomPanel = customPanel != null
    512                 && customPanel.getVisibility() != View.GONE;
    513         final boolean hasTopPanel = topPanel != null
    514                 && topPanel.getVisibility() != View.GONE;
    515         final boolean hasButtonPanel = buttonPanel != null
    516                 && buttonPanel.getVisibility() != View.GONE;
    517 
    518         // Only display the text spacer if we don't have buttons.
    519         if (!hasButtonPanel) {
    520             if (contentPanel != null) {
    521                 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
    522                 if (spacer != null) {
    523                     spacer.setVisibility(View.VISIBLE);
    524                 }
    525             }
    526             mWindow.setCloseOnTouchOutsideIfNotSet(true);
    527         }
    528 
    529         if (hasTopPanel) {
    530             // Only clip scrolling content to padding if we have a title.
    531             if (mScrollView != null) {
    532                 mScrollView.setClipToPadding(true);
    533             }
    534 
    535             // Only show the divider if we have a title.
    536             final View divider;
    537             if (mMessage != null || mListView != null || hasCustomPanel) {
    538                 divider = topPanel.findViewById(R.id.titleDivider);
    539             } else {
    540                 divider = topPanel.findViewById(R.id.titleDividerTop);
    541             }
    542 
    543             if (divider != null) {
    544                 divider.setVisibility(View.VISIBLE);
    545             }
    546         }
    547 
    548         // Update scroll indicators as needed.
    549         if (!hasCustomPanel) {
    550             final View content = mListView != null ? mListView : mScrollView;
    551             if (content != null) {
    552                 final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
    553                         | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
    554                 content.setScrollIndicators(indicators,
    555                         View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
    556             }
    557         }
    558 
    559         final TypedArray a = mContext.obtainStyledAttributes(
    560                 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
    561         setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
    562                 hasTopPanel, hasCustomPanel, hasButtonPanel);
    563         a.recycle();
    564     }
    565 
    566     private void setupCustomContent(ViewGroup customPanel) {
    567         final View customView;
    568         if (mView != null) {
    569             customView = mView;
    570         } else if (mViewLayoutResId != 0) {
    571             final LayoutInflater inflater = LayoutInflater.from(mContext);
    572             customView = inflater.inflate(mViewLayoutResId, customPanel, false);
    573         } else {
    574             customView = null;
    575         }
    576 
    577         final boolean hasCustomView = customView != null;
    578         if (!hasCustomView || !canTextInput(customView)) {
    579             mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
    580                     WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
    581         }
    582 
    583         if (hasCustomView) {
    584             final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
    585             custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
    586 
    587             if (mViewSpacingSpecified) {
    588                 custom.setPadding(
    589                         mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
    590             }
    591 
    592             if (mListView != null) {
    593                 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
    594             }
    595         } else {
    596             customPanel.setVisibility(View.GONE);
    597         }
    598     }
    599 
    600     private void setupTitle(ViewGroup topPanel) {
    601         if (mCustomTitleView != null) {
    602             // Add the custom title view directly to the topPanel layout
    603             LayoutParams lp = new LayoutParams(
    604                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    605 
    606             topPanel.addView(mCustomTitleView, 0, lp);
    607 
    608             // Hide the title template
    609             View titleTemplate = mWindow.findViewById(R.id.title_template);
    610             titleTemplate.setVisibility(View.GONE);
    611         } else {
    612             mIconView = (ImageView) mWindow.findViewById(R.id.icon);
    613 
    614             final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
    615             if (hasTextTitle) {
    616                 // Display the title if a title is supplied, else hide it.
    617                 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
    618                 mTitleView.setText(mTitle);
    619 
    620                 // Do this last so that if the user has supplied any icons we
    621                 // use them instead of the default ones. If the user has
    622                 // specified 0 then make it disappear.
    623                 if (mIconId != 0) {
    624                     mIconView.setImageResource(mIconId);
    625                 } else if (mIcon != null) {
    626                     mIconView.setImageDrawable(mIcon);
    627                 } else {
    628                     // Apply the padding from the icon to ensure the title is
    629                     // aligned correctly.
    630                     mTitleView.setPadding(mIconView.getPaddingLeft(),
    631                             mIconView.getPaddingTop(),
    632                             mIconView.getPaddingRight(),
    633                             mIconView.getPaddingBottom());
    634                     mIconView.setVisibility(View.GONE);
    635                 }
    636             } else {
    637                 // Hide the title template
    638                 final View titleTemplate = mWindow.findViewById(R.id.title_template);
    639                 titleTemplate.setVisibility(View.GONE);
    640                 mIconView.setVisibility(View.GONE);
    641                 topPanel.setVisibility(View.GONE);
    642             }
    643         }
    644     }
    645 
    646     private void setupContent(ViewGroup contentPanel) {
    647         mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView);
    648         mScrollView.setFocusable(false);
    649 
    650         // Special case for users that only want to display a String
    651         mMessageView = (TextView) contentPanel.findViewById(R.id.message);
    652         if (mMessageView == null) {
    653             return;
    654         }
    655 
    656         if (mMessage != null) {
    657             mMessageView.setText(mMessage);
    658         } else {
    659             mMessageView.setVisibility(View.GONE);
    660             mScrollView.removeView(mMessageView);
    661 
    662             if (mListView != null) {
    663                 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent();
    664                 final int childIndex = scrollParent.indexOfChild(mScrollView);
    665                 scrollParent.removeViewAt(childIndex);
    666                 scrollParent.addView(mListView, childIndex,
    667                         new LayoutParams(MATCH_PARENT, MATCH_PARENT));
    668             } else {
    669                 contentPanel.setVisibility(View.GONE);
    670             }
    671         }
    672     }
    673 
    674     private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) {
    675         if (upIndicator != null) {
    676             upIndicator.setVisibility(v.canScrollVertically(-1) ? View.VISIBLE : View.INVISIBLE);
    677         }
    678         if (downIndicator != null) {
    679             downIndicator.setVisibility(v.canScrollVertically(1) ? View.VISIBLE : View.INVISIBLE);
    680         }
    681     }
    682 
    683     private void setupButtons(ViewGroup buttonPanel) {
    684         int BIT_BUTTON_POSITIVE = 1;
    685         int BIT_BUTTON_NEGATIVE = 2;
    686         int BIT_BUTTON_NEUTRAL = 4;
    687         int whichButtons = 0;
    688         mButtonPositive = (Button) buttonPanel.findViewById(R.id.button1);
    689         mButtonPositive.setOnClickListener(mButtonHandler);
    690 
    691         if (TextUtils.isEmpty(mButtonPositiveText)) {
    692             mButtonPositive.setVisibility(View.GONE);
    693         } else {
    694             mButtonPositive.setText(mButtonPositiveText);
    695             mButtonPositive.setVisibility(View.VISIBLE);
    696             whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
    697         }
    698 
    699         mButtonNegative = (Button) buttonPanel.findViewById(R.id.button2);
    700         mButtonNegative.setOnClickListener(mButtonHandler);
    701 
    702         if (TextUtils.isEmpty(mButtonNegativeText)) {
    703             mButtonNegative.setVisibility(View.GONE);
    704         } else {
    705             mButtonNegative.setText(mButtonNegativeText);
    706             mButtonNegative.setVisibility(View.VISIBLE);
    707 
    708             whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
    709         }
    710 
    711         mButtonNeutral = (Button) buttonPanel.findViewById(R.id.button3);
    712         mButtonNeutral.setOnClickListener(mButtonHandler);
    713 
    714         if (TextUtils.isEmpty(mButtonNeutralText)) {
    715             mButtonNeutral.setVisibility(View.GONE);
    716         } else {
    717             mButtonNeutral.setText(mButtonNeutralText);
    718             mButtonNeutral.setVisibility(View.VISIBLE);
    719 
    720             whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
    721         }
    722 
    723         if (shouldCenterSingleButton(mContext)) {
    724             /*
    725              * If we only have 1 button it should be centered on the layout and
    726              * expand to fill 50% of the available space.
    727              */
    728             if (whichButtons == BIT_BUTTON_POSITIVE) {
    729                 centerButton(mButtonPositive);
    730             } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
    731                 centerButton(mButtonNegative);
    732             } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
    733                 centerButton(mButtonNeutral);
    734             }
    735         }
    736 
    737         final boolean hasButtons = whichButtons != 0;
    738         if (!hasButtons) {
    739             buttonPanel.setVisibility(View.GONE);
    740         }
    741     }
    742 
    743     private void centerButton(Button button) {
    744         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
    745         params.gravity = Gravity.CENTER_HORIZONTAL;
    746         params.weight = 0.5f;
    747         button.setLayoutParams(params);
    748         View leftSpacer = mWindow.findViewById(R.id.leftSpacer);
    749         if (leftSpacer != null) {
    750             leftSpacer.setVisibility(View.VISIBLE);
    751         }
    752         View rightSpacer = mWindow.findViewById(R.id.rightSpacer);
    753         if (rightSpacer != null) {
    754             rightSpacer.setVisibility(View.VISIBLE);
    755         }
    756     }
    757 
    758     private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
    759             View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
    760         int fullDark = 0;
    761         int topDark = 0;
    762         int centerDark = 0;
    763         int bottomDark = 0;
    764         int fullBright = 0;
    765         int topBright = 0;
    766         int centerBright = 0;
    767         int bottomBright = 0;
    768         int bottomMedium = 0;
    769 
    770         // If the needsDefaultBackgrounds attribute is set, we know we're
    771         // inheriting from a framework style.
    772         final boolean needsDefaultBackgrounds = a.getBoolean(
    773                 R.styleable.AlertDialog_needsDefaultBackgrounds, true);
    774         if (needsDefaultBackgrounds) {
    775             fullDark = R.drawable.popup_full_dark;
    776             topDark = R.drawable.popup_top_dark;
    777             centerDark = R.drawable.popup_center_dark;
    778             bottomDark = R.drawable.popup_bottom_dark;
    779             fullBright = R.drawable.popup_full_bright;
    780             topBright = R.drawable.popup_top_bright;
    781             centerBright = R.drawable.popup_center_bright;
    782             bottomBright = R.drawable.popup_bottom_bright;
    783             bottomMedium = R.drawable.popup_bottom_medium;
    784         }
    785 
    786         topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
    787         topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
    788         centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
    789         centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
    790 
    791         /* We now set the background of all of the sections of the alert.
    792          * First collect together each section that is being displayed along
    793          * with whether it is on a light or dark background, then run through
    794          * them setting their backgrounds.  This is complicated because we need
    795          * to correctly use the full, top, middle, and bottom graphics depending
    796          * on how many views they are and where they appear.
    797          */
    798 
    799         final View[] views = new View[4];
    800         final boolean[] light = new boolean[4];
    801         View lastView = null;
    802         boolean lastLight = false;
    803 
    804         int pos = 0;
    805         if (hasTitle) {
    806             views[pos] = topPanel;
    807             light[pos] = false;
    808             pos++;
    809         }
    810 
    811         /* The contentPanel displays either a custom text message or
    812          * a ListView. If it's text we should use the dark background
    813          * for ListView we should use the light background. If neither
    814          * are there the contentPanel will be hidden so set it as null.
    815          */
    816         views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
    817         light[pos] = mListView != null;
    818         pos++;
    819 
    820         if (hasCustomView) {
    821             views[pos] = customPanel;
    822             light[pos] = mForceInverseBackground;
    823             pos++;
    824         }
    825 
    826         if (hasButtons) {
    827             views[pos] = buttonPanel;
    828             light[pos] = true;
    829         }
    830 
    831         boolean setView = false;
    832         for (pos = 0; pos < views.length; pos++) {
    833             final View v = views[pos];
    834             if (v == null) {
    835                 continue;
    836             }
    837 
    838             if (lastView != null) {
    839                 if (!setView) {
    840                     lastView.setBackgroundResource(lastLight ? topBright : topDark);
    841                 } else {
    842                     lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
    843                 }
    844                 setView = true;
    845             }
    846 
    847             lastView = v;
    848             lastLight = light[pos];
    849         }
    850 
    851         if (lastView != null) {
    852             if (setView) {
    853                 bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
    854                 bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
    855                 bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
    856 
    857                 // ListViews will use the Bright background, but buttons use the
    858                 // Medium background.
    859                 lastView.setBackgroundResource(
    860                         lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
    861             } else {
    862                 fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
    863                 fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
    864 
    865                 lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
    866             }
    867         }
    868 
    869         final ListView listView = mListView;
    870         if (listView != null && mAdapter != null) {
    871             listView.setAdapter(mAdapter);
    872             final int checkedItem = mCheckedItem;
    873             if (checkedItem > -1) {
    874                 listView.setItemChecked(checkedItem, true);
    875                 listView.setSelection(checkedItem);
    876             }
    877         }
    878     }
    879 
    880     public static class RecycleListView extends ListView {
    881         boolean mRecycleOnMeasure = true;
    882 
    883         public RecycleListView(Context context) {
    884             super(context);
    885         }
    886 
    887         public RecycleListView(Context context, AttributeSet attrs) {
    888             super(context, attrs);
    889         }
    890 
    891         public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) {
    892             super(context, attrs, defStyleAttr);
    893         }
    894 
    895         public RecycleListView(
    896                 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    897             super(context, attrs, defStyleAttr, defStyleRes);
    898         }
    899 
    900         @Override
    901         protected boolean recycleOnMeasure() {
    902             return mRecycleOnMeasure;
    903         }
    904     }
    905 
    906     public static class AlertParams {
    907         public final Context mContext;
    908         public final LayoutInflater mInflater;
    909 
    910         public int mIconId = 0;
    911         public Drawable mIcon;
    912         public int mIconAttrId = 0;
    913         public CharSequence mTitle;
    914         public View mCustomTitleView;
    915         public CharSequence mMessage;
    916         public CharSequence mPositiveButtonText;
    917         public DialogInterface.OnClickListener mPositiveButtonListener;
    918         public CharSequence mNegativeButtonText;
    919         public DialogInterface.OnClickListener mNegativeButtonListener;
    920         public CharSequence mNeutralButtonText;
    921         public DialogInterface.OnClickListener mNeutralButtonListener;
    922         public boolean mCancelable;
    923         public DialogInterface.OnCancelListener mOnCancelListener;
    924         public DialogInterface.OnDismissListener mOnDismissListener;
    925         public DialogInterface.OnKeyListener mOnKeyListener;
    926         public CharSequence[] mItems;
    927         public ListAdapter mAdapter;
    928         public DialogInterface.OnClickListener mOnClickListener;
    929         public int mViewLayoutResId;
    930         public View mView;
    931         public int mViewSpacingLeft;
    932         public int mViewSpacingTop;
    933         public int mViewSpacingRight;
    934         public int mViewSpacingBottom;
    935         public boolean mViewSpacingSpecified = false;
    936         public boolean[] mCheckedItems;
    937         public boolean mIsMultiChoice;
    938         public boolean mIsSingleChoice;
    939         public int mCheckedItem = -1;
    940         public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
    941         public Cursor mCursor;
    942         public String mLabelColumn;
    943         public String mIsCheckedColumn;
    944         public boolean mForceInverseBackground;
    945         public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
    946         public OnPrepareListViewListener mOnPrepareListViewListener;
    947         public boolean mRecycleOnMeasure = true;
    948 
    949         /**
    950          * Interface definition for a callback to be invoked before the ListView
    951          * will be bound to an adapter.
    952          */
    953         public interface OnPrepareListViewListener {
    954 
    955             /**
    956              * Called before the ListView is bound to an adapter.
    957              * @param listView The ListView that will be shown in the dialog.
    958              */
    959             void onPrepareListView(ListView listView);
    960         }
    961 
    962         public AlertParams(Context context) {
    963             mContext = context;
    964             mCancelable = true;
    965             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    966         }
    967 
    968         public void apply(AlertController dialog) {
    969             if (mCustomTitleView != null) {
    970                 dialog.setCustomTitle(mCustomTitleView);
    971             } else {
    972                 if (mTitle != null) {
    973                     dialog.setTitle(mTitle);
    974                 }
    975                 if (mIcon != null) {
    976                     dialog.setIcon(mIcon);
    977                 }
    978                 if (mIconId != 0) {
    979                     dialog.setIcon(mIconId);
    980                 }
    981                 if (mIconAttrId != 0) {
    982                     dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
    983                 }
    984             }
    985             if (mMessage != null) {
    986                 dialog.setMessage(mMessage);
    987             }
    988             if (mPositiveButtonText != null) {
    989                 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
    990                         mPositiveButtonListener, null);
    991             }
    992             if (mNegativeButtonText != null) {
    993                 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
    994                         mNegativeButtonListener, null);
    995             }
    996             if (mNeutralButtonText != null) {
    997                 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
    998                         mNeutralButtonListener, null);
    999             }
   1000             if (mForceInverseBackground) {
   1001                 dialog.setInverseBackgroundForced(true);
   1002             }
   1003             // For a list, the client can either supply an array of items or an
   1004             // adapter or a cursor
   1005             if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
   1006                 createListView(dialog);
   1007             }
   1008             if (mView != null) {
   1009                 if (mViewSpacingSpecified) {
   1010                     dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
   1011                             mViewSpacingBottom);
   1012                 } else {
   1013                     dialog.setView(mView);
   1014                 }
   1015             } else if (mViewLayoutResId != 0) {
   1016                 dialog.setView(mViewLayoutResId);
   1017             }
   1018 
   1019             /*
   1020             dialog.setCancelable(mCancelable);
   1021             dialog.setOnCancelListener(mOnCancelListener);
   1022             if (mOnKeyListener != null) {
   1023                 dialog.setOnKeyListener(mOnKeyListener);
   1024             }
   1025             */
   1026         }
   1027 
   1028         private void createListView(final AlertController dialog) {
   1029             final RecycleListView listView =
   1030                     (RecycleListView) mInflater.inflate(dialog.mListLayout, null);
   1031             final ListAdapter adapter;
   1032 
   1033             if (mIsMultiChoice) {
   1034                 if (mCursor == null) {
   1035                     adapter = new ArrayAdapter<CharSequence>(
   1036                             mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) {
   1037                         @Override
   1038                         public View getView(int position, View convertView, ViewGroup parent) {
   1039                             View view = super.getView(position, convertView, parent);
   1040                             if (mCheckedItems != null) {
   1041                                 boolean isItemChecked = mCheckedItems[position];
   1042                                 if (isItemChecked) {
   1043                                     listView.setItemChecked(position, true);
   1044                                 }
   1045                             }
   1046                             return view;
   1047                         }
   1048                     };
   1049                 } else {
   1050                     adapter = new CursorAdapter(mContext, mCursor, false) {
   1051                         private final int mLabelIndex;
   1052                         private final int mIsCheckedIndex;
   1053 
   1054                         {
   1055                             final Cursor cursor = getCursor();
   1056                             mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
   1057                             mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
   1058                         }
   1059 
   1060                         @Override
   1061                         public void bindView(View view, Context context, Cursor cursor) {
   1062                             CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
   1063                             text.setText(cursor.getString(mLabelIndex));
   1064                             listView.setItemChecked(cursor.getPosition(),
   1065                                     cursor.getInt(mIsCheckedIndex) == 1);
   1066                         }
   1067 
   1068                         @Override
   1069                         public View newView(Context context, Cursor cursor, ViewGroup parent) {
   1070                             return mInflater.inflate(dialog.mMultiChoiceItemLayout,
   1071                                     parent, false);
   1072                         }
   1073 
   1074                     };
   1075                 }
   1076             } else {
   1077                 final int layout;
   1078                 if (mIsSingleChoice) {
   1079                     layout = dialog.mSingleChoiceItemLayout;
   1080                 } else {
   1081                     layout = dialog.mListItemLayout;
   1082                 }
   1083 
   1084                 if (mCursor != null) {
   1085                     adapter = new SimpleCursorAdapter(mContext, layout, mCursor,
   1086                             new String[] { mLabelColumn }, new int[] { R.id.text1 });
   1087                 } else if (mAdapter != null) {
   1088                     adapter = mAdapter;
   1089                 } else {
   1090                     adapter = new CheckedItemAdapter(mContext, layout, R.id.text1, mItems);
   1091                 }
   1092             }
   1093 
   1094             if (mOnPrepareListViewListener != null) {
   1095                 mOnPrepareListViewListener.onPrepareListView(listView);
   1096             }
   1097 
   1098             /* Don't directly set the adapter on the ListView as we might
   1099              * want to add a footer to the ListView later.
   1100              */
   1101             dialog.mAdapter = adapter;
   1102             dialog.mCheckedItem = mCheckedItem;
   1103 
   1104             if (mOnClickListener != null) {
   1105                 listView.setOnItemClickListener(new OnItemClickListener() {
   1106                     @Override
   1107                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
   1108                         mOnClickListener.onClick(dialog.mDialogInterface, position);
   1109                         if (!mIsSingleChoice) {
   1110                             dialog.mDialogInterface.dismiss();
   1111                         }
   1112                     }
   1113                 });
   1114             } else if (mOnCheckboxClickListener != null) {
   1115                 listView.setOnItemClickListener(new OnItemClickListener() {
   1116                     @Override
   1117                     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
   1118                         if (mCheckedItems != null) {
   1119                             mCheckedItems[position] = listView.isItemChecked(position);
   1120                         }
   1121                         mOnCheckboxClickListener.onClick(
   1122                                 dialog.mDialogInterface, position, listView.isItemChecked(position));
   1123                     }
   1124                 });
   1125             }
   1126 
   1127             // Attach a given OnItemSelectedListener to the ListView
   1128             if (mOnItemSelectedListener != null) {
   1129                 listView.setOnItemSelectedListener(mOnItemSelectedListener);
   1130             }
   1131 
   1132             if (mIsSingleChoice) {
   1133                 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
   1134             } else if (mIsMultiChoice) {
   1135                 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
   1136             }
   1137             listView.mRecycleOnMeasure = mRecycleOnMeasure;
   1138             dialog.mListView = listView;
   1139         }
   1140     }
   1141 
   1142     private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> {
   1143         public CheckedItemAdapter(Context context, int resource, int textViewResourceId,
   1144                 CharSequence[] objects) {
   1145             super(context, resource, textViewResourceId, objects);
   1146         }
   1147 
   1148         @Override
   1149         public boolean hasStableIds() {
   1150             return true;
   1151         }
   1152 
   1153         @Override
   1154         public long getItemId(int position) {
   1155             return position;
   1156         }
   1157     }
   1158 }
   1159