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