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