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