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.packageinstaller;
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.graphics.drawable.Drawable;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.text.TextUtils;
28 import android.view.KeyEvent;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.ViewGroup.LayoutParams;
33 import android.view.ViewParent;
34 import android.view.ViewStub;
35 import android.view.Window;
36 import android.view.WindowManager;
37 import android.widget.Button;
38 import android.widget.FrameLayout;
39 import android.widget.ImageView;
40 import android.widget.ListAdapter;
41 import android.widget.ListView;
42 import android.widget.ScrollView;
43 import android.widget.TextView;
44 
45 import androidx.annotation.Nullable;
46 
47 import com.android.packageinstaller.R;
48 
49 import java.lang.ref.WeakReference;
50 
51 public class AlertController {
52     private final Context mContext;
53     private final DialogInterface mDialogInterface;
54     protected final Window mWindow;
55 
56     private CharSequence mTitle;
57     protected CharSequence mMessage;
58     protected ListView mListView;
59     private View mView;
60 
61     private int mViewLayoutResId;
62 
63     private int mViewSpacingLeft;
64     private int mViewSpacingTop;
65     private int mViewSpacingRight;
66     private int mViewSpacingBottom;
67     private boolean mViewSpacingSpecified = false;
68 
69     private Button mButtonPositive;
70     private CharSequence mButtonPositiveText;
71     private Message mButtonPositiveMessage;
72 
73     private Button mButtonNegative;
74     private CharSequence mButtonNegativeText;
75     private Message mButtonNegativeMessage;
76 
77     private Button mButtonNeutral;
78     private CharSequence mButtonNeutralText;
79     private Message mButtonNeutralMessage;
80 
81     protected ScrollView mScrollView;
82 
83     private int mIconId = 0;
84     private Drawable mIcon;
85 
86     private ImageView mIconView;
87     private TextView mTitleView;
88     protected TextView mMessageView;
89 
90     private ListAdapter mAdapter;
91 
92     private int mAlertDialogLayout;
93     private int mButtonPanelSideLayout;
94     private int mListLayout;
95     private int mMultiChoiceItemLayout;
96     private int mSingleChoiceItemLayout;
97     private int mListItemLayout;
98 
99     private boolean mShowTitle;
100 
101     private Handler mHandler;
102 
103     private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
104         @Override
105         public void onClick(View v) {
106             final Message m;
107             if (v == mButtonPositive && mButtonPositiveMessage != null) {
108                 m = Message.obtain(mButtonPositiveMessage);
109             } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
110                 m = Message.obtain(mButtonNegativeMessage);
111             } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
112                 m = Message.obtain(mButtonNeutralMessage);
113             } else {
114                 m = null;
115             }
116 
117             if (m != null) {
118                 m.sendToTarget();
119             }
120 
121             // Post a message so we dismiss after the above handlers are executed
122             mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
123                     .sendToTarget();
124         }
125     };
126 
127     private static final class ButtonHandler extends Handler {
128         // Button clicks have Message.what as the BUTTON{1,2,3} constant
129         private static final int MSG_DISMISS_DIALOG = 1;
130 
131         private WeakReference<DialogInterface> mDialog;
132 
ButtonHandler(DialogInterface dialog)133         public ButtonHandler(DialogInterface dialog) {
134             mDialog = new WeakReference<>(dialog);
135         }
136 
137         @Override
handleMessage(Message msg)138         public void handleMessage(Message msg) {
139             switch (msg.what) {
140 
141                 case DialogInterface.BUTTON_POSITIVE:
142                 case DialogInterface.BUTTON_NEGATIVE:
143                 case DialogInterface.BUTTON_NEUTRAL:
144                     ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
145                     break;
146 
147                 case MSG_DISMISS_DIALOG:
148                     ((DialogInterface) msg.obj).dismiss();
149             }
150         }
151     }
152 
AlertController(Context context, DialogInterface di, Window window)153     public AlertController(Context context, DialogInterface di, Window window) {
154         mContext = context;
155         mDialogInterface = di;
156         mWindow = window;
157         mHandler = new ButtonHandler(di);
158 
159         final TypedArray a = context.obtainStyledAttributes(null,
160                 R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
161 
162         mAlertDialogLayout = a.getResourceId(
163                 R.styleable.AlertDialog_layout, R.layout.alert_dialog_material);
164         mButtonPanelSideLayout = a.getResourceId(
165                 R.styleable.AlertDialog_buttonPanelSideLayout, 0);
166         mListLayout = a.getResourceId(
167                 R.styleable.AlertDialog_listLayout, R.layout.select_dialog_material);
168 
169         mMultiChoiceItemLayout = a.getResourceId(
170                 R.styleable.AlertDialog_multiChoiceItemLayout,
171                 R.layout.select_dialog_multichoice_material);
172         mSingleChoiceItemLayout = a.getResourceId(
173                 R.styleable.AlertDialog_singleChoiceItemLayout,
174                 R.layout.select_dialog_singlechoice_material);
175         mListItemLayout = a.getResourceId(
176                 R.styleable.AlertDialog_listItemLayout,
177                 R.layout.select_dialog_item_material);
178         mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
179 
180         a.recycle();
181 
182         /* We use a custom title so never request a window title */
183         window.requestFeature(Window.FEATURE_NO_TITLE);
184     }
185 
canTextInput(View v)186     static boolean canTextInput(View v) {
187         if (v.onCheckIsTextEditor()) {
188             return true;
189         }
190 
191         if (!(v instanceof ViewGroup)) {
192             return false;
193         }
194 
195         ViewGroup vg = (ViewGroup)v;
196         int i = vg.getChildCount();
197         while (i > 0) {
198             i--;
199             v = vg.getChildAt(i);
200             if (canTextInput(v)) {
201                 return true;
202             }
203         }
204 
205         return false;
206     }
207 
installContent()208     public void installContent() {
209         mWindow.setContentView(mAlertDialogLayout);
210         setupView();
211     }
212 
setTitle(CharSequence title)213     public void setTitle(CharSequence title) {
214         mTitle = title;
215         if (mTitleView != null) {
216             mTitleView.setText(title);
217         }
218         mWindow.setTitle(title);
219     }
220 
221     /**
222      * Set the view resource to display in the dialog.
223      */
setView(int layoutResId)224     public void setView(int layoutResId) {
225         mView = null;
226         mViewLayoutResId = layoutResId;
227         mViewSpacingSpecified = false;
228     }
229 
230     /**
231      * Sets a click listener or a message to be sent when the button is clicked.
232      * You only need to pass one of {@code listener} or {@code msg}.
233      *
234      * @param whichButton Which button, can be one of
235      *            {@link DialogInterface#BUTTON_POSITIVE},
236      *            {@link DialogInterface#BUTTON_NEGATIVE}, or
237      *            {@link DialogInterface#BUTTON_NEUTRAL}
238      * @param text The text to display in positive button.
239      * @param listener The {@link DialogInterface.OnClickListener} to use.
240      * @param msg The {@link Message} to be sent when clicked.
241      */
setButton(int whichButton, CharSequence text, DialogInterface.OnClickListener listener, Message msg)242     public void setButton(int whichButton, CharSequence text,
243             DialogInterface.OnClickListener listener, Message msg) {
244 
245         if (msg == null && listener != null) {
246             msg = mHandler.obtainMessage(whichButton, listener);
247         }
248 
249         switch (whichButton) {
250 
251             case DialogInterface.BUTTON_POSITIVE:
252                 mButtonPositiveText = text;
253                 mButtonPositiveMessage = msg;
254                 break;
255 
256             case DialogInterface.BUTTON_NEGATIVE:
257                 mButtonNegativeText = text;
258                 mButtonNegativeMessage = msg;
259                 break;
260 
261             case DialogInterface.BUTTON_NEUTRAL:
262                 mButtonNeutralText = text;
263                 mButtonNeutralMessage = msg;
264                 break;
265 
266             default:
267                 throw new IllegalArgumentException("Button does not exist");
268         }
269     }
270 
271     /**
272      * Specifies the icon to display next to the alert title.
273      *
274      * @param resId the resource identifier of the drawable to use as the icon,
275      *            or 0 for no icon
276      */
setIcon(int resId)277     public void setIcon(int resId) {
278         mIcon = null;
279         mIconId = resId;
280 
281         if (mIconView != null) {
282             if (resId != 0) {
283                 mIconView.setVisibility(View.VISIBLE);
284                 mIconView.setImageResource(mIconId);
285             } else {
286                 mIconView.setVisibility(View.GONE);
287             }
288         }
289     }
290 
291     /**
292      * Specifies the icon to display next to the alert title.
293      *
294      * @param icon the drawable to use as the icon or null for no icon
295      */
setIcon(Drawable icon)296     public void setIcon(Drawable icon) {
297         mIcon = icon;
298         mIconId = 0;
299 
300         if (mIconView != null) {
301             if (icon != null) {
302                 mIconView.setVisibility(View.VISIBLE);
303                 mIconView.setImageDrawable(icon);
304             } else {
305                 mIconView.setVisibility(View.GONE);
306             }
307         }
308     }
309 
getButton(int whichButton)310     public Button getButton(int whichButton) {
311         switch (whichButton) {
312             case DialogInterface.BUTTON_POSITIVE:
313                 return mButtonPositive;
314             case DialogInterface.BUTTON_NEGATIVE:
315                 return mButtonNegative;
316             case DialogInterface.BUTTON_NEUTRAL:
317                 return mButtonNeutral;
318             default:
319                 return null;
320         }
321     }
322 
323     @SuppressWarnings({"UnusedDeclaration"})
onKeyDown(int keyCode, KeyEvent event)324     public boolean onKeyDown(int keyCode, KeyEvent event) {
325         return mScrollView != null && mScrollView.executeKeyEvent(event);
326     }
327 
328     @SuppressWarnings({"UnusedDeclaration"})
onKeyUp(int keyCode, KeyEvent event)329     public boolean onKeyUp(int keyCode, KeyEvent event) {
330         return mScrollView != null && mScrollView.executeKeyEvent(event);
331     }
332 
333     /**
334      * Resolves whether a custom or default panel should be used. Removes the
335      * default panel if a custom panel should be used. If the resolved panel is
336      * a view stub, inflates before returning.
337      *
338      * @param customPanel the custom panel
339      * @param defaultPanel the default panel
340      * @return the panel to use
341      */
342     @Nullable
resolvePanel(@ullable View customPanel, @Nullable View defaultPanel)343     private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
344         if (customPanel == null) {
345             // Inflate the default panel, if needed.
346             if (defaultPanel instanceof ViewStub) {
347                 defaultPanel = ((ViewStub) defaultPanel).inflate();
348             }
349 
350             return (ViewGroup) defaultPanel;
351         }
352 
353         // Remove the default panel entirely.
354         if (defaultPanel != null) {
355             final ViewParent parent = defaultPanel.getParent();
356             if (parent instanceof ViewGroup) {
357                 ((ViewGroup) parent).removeView(defaultPanel);
358             }
359         }
360 
361         // Inflate the custom panel, if needed.
362         if (customPanel instanceof ViewStub) {
363             customPanel = ((ViewStub) customPanel).inflate();
364         }
365 
366         return (ViewGroup) customPanel;
367     }
368 
setupView()369     private void setupView() {
370         final View parentPanel = mWindow.findViewById(R.id.parentPanel);
371         final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
372         final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
373         final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
374 
375         // Install custom content before setting up the title or buttons so
376         // that we can handle panel overrides.
377         final ViewGroup customPanel = parentPanel.findViewById(R.id.customPanel);
378         setupCustomContent(customPanel);
379 
380         final View customTopPanel = customPanel.findViewById(R.id.topPanel);
381         final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
382         final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
383 
384         // Resolve the correct panels and remove the defaults, if needed.
385         final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
386         final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
387         final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
388 
389         setupContent(contentPanel);
390         setupButtons(buttonPanel);
391         setupTitle(topPanel);
392 
393         final boolean hasCustomPanel = customPanel != null
394                 && customPanel.getVisibility() != View.GONE;
395         final boolean hasTopPanel = topPanel != null
396                 && topPanel.getVisibility() != View.GONE;
397         final boolean hasButtonPanel = buttonPanel != null
398                 && buttonPanel.getVisibility() != View.GONE;
399 
400         if (!parentPanel.isInTouchMode()) {
401             final View content = hasCustomPanel ? customPanel : contentPanel;
402             if (!requestFocusForContent(content)) {
403                 requestFocusForDefaultButton();
404             }
405         }
406 
407         if (hasTopPanel) {
408             // Only clip scrolling content to padding if we have a title.
409             if (mScrollView != null) {
410                 mScrollView.setClipToPadding(true);
411             }
412 
413             // Only show the divider if we have a title.
414             View divider = null;
415             if (mMessage != null || hasCustomPanel) {
416                 divider = topPanel.findViewById(R.id.titleDividerNoCustom);
417             }
418 
419             if (divider != null) {
420                 divider.setVisibility(View.VISIBLE);
421             }
422         } else {
423             if (contentPanel != null) {
424                 final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
425                 if (spacer != null) {
426                     spacer.setVisibility(View.VISIBLE);
427                 }
428             }
429         }
430 
431         // Update scroll indicators as needed.
432         if (!hasCustomPanel) {
433             final View content = mScrollView;
434             if (content != null) {
435                 final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
436                         | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
437                 content.setScrollIndicators(indicators,
438                         View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
439             }
440         }
441 
442         final TypedArray a = mContext.obtainStyledAttributes(
443                 null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
444         setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
445                 hasTopPanel, hasCustomPanel, hasButtonPanel);
446         a.recycle();
447     }
448 
requestFocusForContent(View content)449     private boolean requestFocusForContent(View content) {
450         return content != null && content.requestFocus();
451     }
452 
requestFocusForDefaultButton()453     private void requestFocusForDefaultButton() {
454         if (mButtonPositive.getVisibility() == View.VISIBLE) {
455             mButtonPositive.requestFocus();
456         } else if (mButtonNegative.getVisibility() == View.VISIBLE) {
457             mButtonNegative.requestFocus();
458         } else if (mButtonNeutral.getVisibility() == View.VISIBLE) {
459             mButtonNeutral.requestFocus();
460         }
461     }
462 
setupCustomContent(ViewGroup customPanel)463     private void setupCustomContent(ViewGroup customPanel) {
464         final View customView;
465         if (mView != null) {
466             customView = mView;
467         } else if (mViewLayoutResId != 0) {
468             final LayoutInflater inflater = LayoutInflater.from(mContext);
469             customView = inflater.inflate(mViewLayoutResId, customPanel, false);
470         } else {
471             customView = null;
472         }
473 
474         final boolean hasCustomView = customView != null;
475         if (!hasCustomView || !canTextInput(customView)) {
476             mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
477                     WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
478         }
479 
480         if (hasCustomView) {
481             final FrameLayout custom = mWindow.findViewById(R.id.custom);
482             custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
483 
484             if (mViewSpacingSpecified) {
485                 custom.setPadding(
486                         mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
487             }
488         } else {
489             customPanel.setVisibility(View.GONE);
490         }
491     }
492 
setupTitle(ViewGroup topPanel)493     private void setupTitle(ViewGroup topPanel) {
494         mIconView = mWindow.findViewById(R.id.icon);
495 
496         final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
497         if (hasTextTitle && mShowTitle) {
498             // Display the title if a title is supplied, else hide it.
499             mTitleView = mWindow.findViewById(R.id.alertTitle);
500             mTitleView.setText(mTitle);
501 
502             // Do this last so that if the user has supplied any icons we
503             // use them instead of the default ones. If the user has
504             // specified 0 then make it disappear.
505             if (mIconId != 0) {
506                 mIconView.setImageResource(mIconId);
507             } else if (mIcon != null) {
508                 mIconView.setImageDrawable(mIcon);
509             } else {
510                 // Apply the padding from the icon to ensure the title is
511                 // aligned correctly.
512                 mTitleView.setPadding(mIconView.getPaddingLeft(),
513                         mIconView.getPaddingTop(),
514                         mIconView.getPaddingRight(),
515                         mIconView.getPaddingBottom());
516                 mIconView.setVisibility(View.GONE);
517             }
518         } else {
519             // Hide the title template
520             final View titleTemplate = mWindow.findViewById(R.id.title_template);
521             titleTemplate.setVisibility(View.GONE);
522             mIconView.setVisibility(View.GONE);
523             topPanel.setVisibility(View.GONE);
524         }
525     }
526 
setupContent(ViewGroup contentPanel)527     private void setupContent(ViewGroup contentPanel) {
528         mScrollView = contentPanel.findViewById(R.id.scrollView);
529         mScrollView.setFocusable(false);
530 
531         // Special case for users that only want to display a String
532         mMessageView = contentPanel.findViewById(R.id.message);
533         if (mMessageView == null) {
534             return;
535         }
536 
537         mMessageView.setVisibility(View.GONE);
538         mScrollView.removeView(mMessageView);
539 
540         contentPanel.setVisibility(View.GONE);
541     }
542 
setupButtons(ViewGroup buttonPanel)543     private void setupButtons(ViewGroup buttonPanel) {
544         int BIT_BUTTON_POSITIVE = 1;
545         int BIT_BUTTON_NEGATIVE = 2;
546         int BIT_BUTTON_NEUTRAL = 4;
547         int whichButtons = 0;
548         mButtonPositive = buttonPanel.findViewById(R.id.button1);
549         mButtonPositive.setOnClickListener(mButtonHandler);
550 
551         if (TextUtils.isEmpty(mButtonPositiveText)) {
552             mButtonPositive.setVisibility(View.GONE);
553         } else {
554             mButtonPositive.setText(mButtonPositiveText);
555             mButtonPositive.setVisibility(View.VISIBLE);
556             whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
557         }
558 
559         mButtonNegative = buttonPanel.findViewById(R.id.button2);
560         mButtonNegative.setOnClickListener(mButtonHandler);
561 
562         if (TextUtils.isEmpty(mButtonNegativeText)) {
563             mButtonNegative.setVisibility(View.GONE);
564         } else {
565             mButtonNegative.setText(mButtonNegativeText);
566             mButtonNegative.setVisibility(View.VISIBLE);
567 
568             whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
569         }
570 
571         mButtonNeutral = buttonPanel.findViewById(R.id.button3);
572         mButtonNeutral.setOnClickListener(mButtonHandler);
573 
574         if (TextUtils.isEmpty(mButtonNeutralText)) {
575             mButtonNeutral.setVisibility(View.GONE);
576         } else {
577             mButtonNeutral.setText(mButtonNeutralText);
578             mButtonNeutral.setVisibility(View.VISIBLE);
579 
580             whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
581         }
582 
583         final boolean hasButtons = whichButtons != 0;
584         if (!hasButtons) {
585             buttonPanel.setVisibility(View.GONE);
586         }
587     }
588 
setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel, View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons)589     private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
590             View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
591         int fullDark = 0;
592         int topDark = 0;
593         int centerDark = 0;
594         int bottomDark = 0;
595         int fullBright = 0;
596         int topBright = 0;
597         int centerBright = 0;
598         int bottomBright = 0;
599         int bottomMedium = 0;
600 
601         topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
602         topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
603         centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
604         centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
605 
606         /* We now set the background of all of the sections of the alert.
607          * First collect together each section that is being displayed along
608          * with whether it is on a light or dark background, then run through
609          * them setting their backgrounds.  This is complicated because we need
610          * to correctly use the full, top, middle, and bottom graphics depending
611          * on how many views they are and where they appear.
612          */
613 
614         final View[] views = new View[4];
615         final boolean[] light = new boolean[4];
616         View lastView = null;
617         boolean lastLight = false;
618 
619         int pos = 0;
620         if (hasTitle) {
621             views[pos] = topPanel;
622             light[pos] = false;
623             pos++;
624         }
625 
626         /* The contentPanel displays either a custom text message or
627          * a ListView. If it's text we should use the dark background
628          * for ListView we should use the light background. PIA does not use
629          * a list view. Hence, we set it to use dark background.  If neither
630          * are there the contentPanel will be hidden so set it as null.
631          */
632         views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
633         light[pos] = false;
634         pos++;
635 
636         if (hasCustomView) {
637             views[pos] = customPanel;
638             light[pos] = false;
639             pos++;
640         }
641 
642         if (hasButtons) {
643             views[pos] = buttonPanel;
644             light[pos] = true;
645         }
646 
647         boolean setView = false;
648         for (pos = 0; pos < views.length; pos++) {
649             final View v = views[pos];
650             if (v == null) {
651                 continue;
652             }
653 
654             if (lastView != null) {
655                 if (!setView) {
656                     lastView.setBackgroundResource(lastLight ? topBright : topDark);
657                 } else {
658                     lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
659                 }
660                 setView = true;
661             }
662 
663             lastView = v;
664             lastLight = light[pos];
665         }
666 
667         if (lastView != null) {
668             if (setView) {
669                 bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
670                 bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
671                 bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
672 
673                 // ListViews will use the Bright background, but buttons use the
674                 // Medium background.
675                 lastView.setBackgroundResource(
676                         lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
677             } else {
678                 fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
679                 fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
680 
681                 lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
682             }
683         }
684     }
685 }
686