• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.car.ui;
17 
18 import static android.view.WindowInsets.Type.ime;
19 
20 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TITLE_TO_CONTENT_AREA;
21 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.ADD_DESC_TO_CONTENT_AREA;
22 import static com.android.car.ui.imewidescreen.CarUiImeWideScreenController.WIDE_SCREEN_ACTION;
23 
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.content.Context;
27 import android.content.DialogInterface;
28 import android.database.Cursor;
29 import android.graphics.drawable.Drawable;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.text.Editable;
33 import android.text.InputFilter;
34 import android.text.TextUtils;
35 import android.text.TextWatcher;
36 import android.text.method.LinkMovementMethod;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.inputmethod.InputMethodManager;
41 import android.widget.AdapterView;
42 import android.widget.EditText;
43 import android.widget.ImageView;
44 import android.widget.ListAdapter;
45 import android.widget.TextView;
46 
47 import androidx.annotation.ArrayRes;
48 import androidx.annotation.AttrRes;
49 import androidx.annotation.DrawableRes;
50 import androidx.annotation.NonNull;
51 import androidx.annotation.StringRes;
52 import androidx.recyclerview.widget.LinearLayoutManager;
53 import androidx.recyclerview.widget.RecyclerView;
54 
55 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
56 import com.android.car.ui.recyclerview.CarUiRadioButtonListItemAdapter;
57 import com.android.car.ui.utils.CarUiUtils;
58 
59 /**
60  * Wrapper for AlertDialog.Builder
61  */
62 public class AlertDialogBuilder {
63 
64     private AlertDialog.Builder mBuilder;
65     private Context mContext;
66     private boolean mPositiveButtonSet;
67     private boolean mNeutralButtonSet;
68     private boolean mNegativeButtonSet;
69     private CharSequence mTitle;
70     private CharSequence mSubtitle;
71     private Drawable mIcon;
72     private boolean mIconTinted;
73     private EditText mCarUiEditText;
74     private InputMethodManager mInputMethodManager;
75     private String mWideScreenTitle;
76     private String mWideScreenTitleDesc;
77     private ViewGroup mRoot;
78     private boolean mAllowDismissButton = true;
79     private boolean mHasSingleChoiceBodyButton = false;
80 
81     private final TextWatcher mTextWatcherWideScreen = new TextWatcher() {
82         @Override
83         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
84 
85         }
86 
87         @Override
88         public void onTextChanged(CharSequence s, int start, int before, int count) {
89             Bundle bundle = new Bundle();
90             String titleString = mWideScreenTitle != null ? mWideScreenTitle : mTitle.toString();
91             bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, titleString);
92             bundle.putString(ADD_DESC_TO_CONTENT_AREA, s.toString());
93             mInputMethodManager.sendAppPrivateCommand(mCarUiEditText, WIDE_SCREEN_ACTION,
94                     bundle);
95         }
96 
97         @Override
98         public void afterTextChanged(Editable s) {
99 
100         }
101     };
102 
103     // Whenever the IME is closed and opened again, the title and desc information needs to be
104     // passed to the IME to be rendered. If the information is not passed to the IME the content
105     // area of the IME will render nothing into the content area.
106     private final View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener = (v, insets) -> {
107         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
108             // WindowInsets.isVisible() is only available on R or above
109             return v.onApplyWindowInsets(insets);
110         }
111 
112         if (insets.isVisible(ime())) {
113             Bundle bundle = new Bundle();
114             String title = mWideScreenTitle != null ? mWideScreenTitle : mTitle.toString();
115             bundle.putString(ADD_DESC_TITLE_TO_CONTENT_AREA, title);
116             if (mWideScreenTitleDesc != null) {
117                 bundle.putString(ADD_DESC_TO_CONTENT_AREA, mWideScreenTitleDesc);
118             }
119             mInputMethodManager.sendAppPrivateCommand(mCarUiEditText, WIDE_SCREEN_ACTION,
120                     bundle);
121         }
122         return v.onApplyWindowInsets(insets);
123     };
124 
125     private final AlertDialog.OnDismissListener mOnDismissListener = dialog -> {
126         if (mRoot != null) {
127             mRoot.setOnApplyWindowInsetsListener(null);
128         }
129     };
130 
AlertDialogBuilder(Context context)131     public AlertDialogBuilder(Context context) {
132         // Resource id specified as 0 uses the parent contexts resolved value for alertDialogTheme.
133         this(context, /* themeResId= */0);
134     }
135 
AlertDialogBuilder(Context context, int themeResId)136     public AlertDialogBuilder(Context context, int themeResId) {
137         mBuilder = new AlertDialog.Builder(context, themeResId);
138         mInputMethodManager = (InputMethodManager)
139                 context.getSystemService(Context.INPUT_METHOD_SERVICE);
140         mContext = context;
141     }
142 
getContext()143     public Context getContext() {
144         return mBuilder.getContext();
145     }
146 
147     /**
148      * Set the title using the given resource id.
149      *
150      * @return This Builder object to allow for chaining of calls to set methods
151      */
setTitle(@tringRes int titleId)152     public AlertDialogBuilder setTitle(@StringRes int titleId) {
153         return setTitle(mContext.getText(titleId));
154     }
155 
156     /**
157      * Set the title displayed in the {@link Dialog}.
158      *
159      * @return This Builder object to allow for chaining of calls to set methods
160      */
setTitle(CharSequence title)161     public AlertDialogBuilder setTitle(CharSequence title) {
162         mTitle = title;
163         mBuilder.setTitle(title);
164         return this;
165     }
166 
167     /**
168      * Sets a subtitle to be displayed in the {@link Dialog}.
169      *
170      * @return This Builder object to allow for chaining of calls to set methods
171      */
setSubtitle(@tringRes int subtitle)172     public AlertDialogBuilder setSubtitle(@StringRes int subtitle) {
173         return setSubtitle(mContext.getString(subtitle));
174     }
175 
176     /**
177      * Sets a subtitle to be displayed in the {@link Dialog}.
178      *
179      * @return This Builder object to allow for chaining of calls to set methods
180      */
setSubtitle(CharSequence subtitle)181     public AlertDialogBuilder setSubtitle(CharSequence subtitle) {
182         mSubtitle = subtitle;
183         return this;
184     }
185 
186     /**
187      * Set the message to display using the given resource id.
188      *
189      * @return This Builder object to allow for chaining of calls to set methods
190      */
setMessage(@tringRes int messageId)191     public AlertDialogBuilder setMessage(@StringRes int messageId) {
192         mBuilder.setMessage(messageId);
193         return this;
194     }
195 
196     /**
197      * Set the message to display.
198      *
199      * @return This Builder object to allow for chaining of calls to set methods
200      */
setMessage(CharSequence message)201     public AlertDialogBuilder setMessage(CharSequence message) {
202         mBuilder.setMessage(message);
203         return this;
204     }
205 
206     /**
207      * Set the resource id of the {@link Drawable} to be used in the title.
208      * <p>
209      * Takes precedence over values set using {@link #setIcon(Drawable)}.
210      *
211      * @return This Builder object to allow for chaining of calls to set methods
212      */
setIcon(@rawableRes int iconId)213     public AlertDialogBuilder setIcon(@DrawableRes int iconId) {
214         return setIcon(mContext.getDrawable(iconId));
215     }
216 
217     /**
218      * Set the {@link Drawable} to be used in the title.
219      * <p>
220      * <strong>Note:</strong> To ensure consistent styling, the drawable
221      * should be inflated or constructed using the alert dialog's themed
222      * context obtained via {@link #getContext()}.
223      *
224      * @return this Builder object to allow for chaining of calls to set
225      * methods
226      */
setIcon(Drawable icon)227     public AlertDialogBuilder setIcon(Drawable icon) {
228         mIcon = icon;
229         return this;
230     }
231 
232     /**
233      * Whether the icon provided by {@link #setIcon(Drawable)} should be
234      * tinted with the default system color.
235      *
236      * @return this Builder object to allow for chaining of calls to set
237      * methods.
238      */
setIconTinted(boolean tinted)239     public AlertDialogBuilder setIconTinted(boolean tinted) {
240         mIconTinted = tinted;
241         return this;
242     }
243 
244     /**
245      * Set an icon as supplied by a theme attribute. e.g.
246      * {@link android.R.attr#alertDialogIcon}.
247      * <p>
248      * Takes precedence over values set using {@link #setIcon(Drawable)}.
249      *
250      * @param attrId ID of a theme attribute that points to a drawable resource.
251      */
setIconAttribute(@ttrRes int attrId)252     public AlertDialogBuilder setIconAttribute(@AttrRes int attrId) {
253         mBuilder.setIconAttribute(attrId);
254         return this;
255     }
256 
257     /**
258      * Set a listener to be invoked when the positive button of the dialog is pressed.
259      *
260      * @param textId   The resource id of the text to display in the positive button
261      * @param listener The {@link DialogInterface.OnClickListener} to use.
262      * @return This Builder object to allow for chaining of calls to set methods
263      */
setPositiveButton(@tringRes int textId, final DialogInterface.OnClickListener listener)264     public AlertDialogBuilder setPositiveButton(@StringRes int textId,
265             final DialogInterface.OnClickListener listener) {
266         mBuilder.setPositiveButton(textId, listener);
267         mPositiveButtonSet = true;
268         return this;
269     }
270 
271     /**
272      * Set a listener to be invoked when the positive button of the dialog is pressed.
273      *
274      * @param text     The text to display in the positive button
275      * @param listener The {@link DialogInterface.OnClickListener} to use.
276      * @return This Builder object to allow for chaining of calls to set methods
277      */
setPositiveButton(CharSequence text, final DialogInterface.OnClickListener listener)278     public AlertDialogBuilder setPositiveButton(CharSequence text,
279             final DialogInterface.OnClickListener listener) {
280         mBuilder.setPositiveButton(text, listener);
281         mPositiveButtonSet = true;
282         return this;
283     }
284 
285     /**
286      * Set a listener to be invoked when the negative button of the dialog is pressed.
287      *
288      * @param textId   The resource id of the text to display in the negative button
289      * @param listener The {@link DialogInterface.OnClickListener} to use.
290      * @return This Builder object to allow for chaining of calls to set methods
291      */
setNegativeButton(@tringRes int textId, final DialogInterface.OnClickListener listener)292     public AlertDialogBuilder setNegativeButton(@StringRes int textId,
293             final DialogInterface.OnClickListener listener) {
294         mBuilder.setNegativeButton(textId, listener);
295         mNegativeButtonSet = true;
296         return this;
297     }
298 
299     /**
300      * Set a listener to be invoked when the negative button of the dialog is pressed.
301      *
302      * @param text     The text to display in the negative button
303      * @param listener The {@link DialogInterface.OnClickListener} to use.
304      * @return This Builder object to allow for chaining of calls to set methods
305      */
setNegativeButton(CharSequence text, final DialogInterface.OnClickListener listener)306     public AlertDialogBuilder setNegativeButton(CharSequence text,
307             final DialogInterface.OnClickListener listener) {
308         mBuilder.setNegativeButton(text, listener);
309         mNegativeButtonSet = true;
310         return this;
311     }
312 
313     /**
314      * Set a listener to be invoked when the neutral button of the dialog is pressed.
315      *
316      * @param textId   The resource id of the text to display in the neutral button
317      * @param listener The {@link DialogInterface.OnClickListener} to use.
318      * @return This Builder object to allow for chaining of calls to set methods
319      */
setNeutralButton(@tringRes int textId, final DialogInterface.OnClickListener listener)320     public AlertDialogBuilder setNeutralButton(@StringRes int textId,
321             final DialogInterface.OnClickListener listener) {
322         mBuilder.setNeutralButton(textId, listener);
323         mNeutralButtonSet = true;
324         return this;
325     }
326 
327     /**
328      * Set a listener to be invoked when the neutral button of the dialog is pressed.
329      *
330      * @param text     The text to display in the neutral button
331      * @param listener The {@link DialogInterface.OnClickListener} to use.
332      * @return This Builder object to allow for chaining of calls to set methods
333      */
setNeutralButton(CharSequence text, final DialogInterface.OnClickListener listener)334     public AlertDialogBuilder setNeutralButton(CharSequence text,
335             final DialogInterface.OnClickListener listener) {
336         mBuilder.setNeutralButton(text, listener);
337         mNeutralButtonSet = true;
338         return this;
339     }
340 
341     /**
342      * Sets whether the dialog is cancelable or not.  Default is true.
343      *
344      * @return This Builder object to allow for chaining of calls to set methods
345      */
setCancelable(boolean cancelable)346     public AlertDialogBuilder setCancelable(boolean cancelable) {
347         mBuilder.setCancelable(cancelable);
348         return this;
349     }
350 
351     /**
352      * Sets the callback that will be called if the dialog is canceled.
353      *
354      * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than
355      * being canceled or one of the supplied choices being selected.
356      * If you are interested in listening for all cases where the dialog is dismissed
357      * and not just when it is canceled, see
358      * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
359      * setOnDismissListener}.</p>
360      *
361      * @return This Builder object to allow for chaining of calls to set methods
362      * @see #setCancelable(boolean)
363      * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
364      */
setOnCancelListener( DialogInterface.OnCancelListener onCancelListener)365     public AlertDialogBuilder setOnCancelListener(
366             DialogInterface.OnCancelListener onCancelListener) {
367         mBuilder.setOnCancelListener(onCancelListener);
368         return this;
369     }
370 
371     /**
372      * Sets the callback that will be called when the dialog is dismissed for any reason.
373      *
374      * @return This Builder object to allow for chaining of calls to set methods
375      */
setOnDismissListener( DialogInterface.OnDismissListener onDismissListener)376     public AlertDialogBuilder setOnDismissListener(
377             DialogInterface.OnDismissListener onDismissListener) {
378         mBuilder.setOnDismissListener(onDismissListener);
379         return this;
380     }
381 
382     /**
383      * Sets the callback that will be called if a key is dispatched to the dialog.
384      *
385      * @return This Builder object to allow for chaining of calls to set methods
386      */
setOnKeyListener(DialogInterface.OnKeyListener onKeyListener)387     public AlertDialogBuilder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener) {
388         mBuilder.setOnKeyListener(onKeyListener);
389         return this;
390     }
391 
392     /**
393      * Set a list of items to be displayed in the dialog as the content, you will be notified of the
394      * selected item via the supplied listener. This should be an array type i.e. R.array.foo
395      *
396      * @return This Builder object to allow for chaining of calls to set methods
397      */
setItems(@rrayRes int itemsId, final DialogInterface.OnClickListener listener)398     public AlertDialogBuilder setItems(@ArrayRes int itemsId,
399             final DialogInterface.OnClickListener listener) {
400         mBuilder.setItems(itemsId, listener);
401         mHasSingleChoiceBodyButton = true;
402         return this;
403     }
404 
405     /**
406      * Set a list of items to be displayed in the dialog as the content, you will be notified of the
407      * selected item via the supplied listener.
408      *
409      * @return This Builder object to allow for chaining of calls to set methods
410      */
setItems(CharSequence[] items, final DialogInterface.OnClickListener listener)411     public AlertDialogBuilder setItems(CharSequence[] items,
412             final DialogInterface.OnClickListener listener) {
413         mBuilder.setItems(items, listener);
414         mHasSingleChoiceBodyButton = true;
415         return this;
416     }
417 
418     /**
419      * This was not supposed to be in the Chassis API because it allows custom views.
420      *
421      * @deprecated Use {@link #setAdapter(CarUiListItemAdapter)} instead.
422      */
423     @Deprecated
setAdapter(final ListAdapter adapter, final DialogInterface.OnClickListener listener)424     public AlertDialogBuilder setAdapter(final ListAdapter adapter,
425             final DialogInterface.OnClickListener listener) {
426         mBuilder.setAdapter(adapter, listener);
427         mHasSingleChoiceBodyButton = true;
428         return this;
429     }
430 
431     /**
432      * Display all the {@link com.android.car.ui.recyclerview.CarUiListItem CarUiListItems} in a
433      * {@link CarUiListItemAdapter}. You should set click listeners on the CarUiListItems as
434      * opposed to a callback in this function.
435      */
setAdapter(final CarUiListItemAdapter adapter)436     public AlertDialogBuilder setAdapter(final CarUiListItemAdapter adapter) {
437         setCustomList(adapter);
438         mHasSingleChoiceBodyButton = true;
439         return this;
440     }
441 
setCustomList(@onNull CarUiListItemAdapter adapter)442     private void setCustomList(@NonNull CarUiListItemAdapter adapter) {
443         View customList = LayoutInflater.from(mContext).inflate(
444                 R.layout.car_ui_alert_dialog_list, null);
445         RecyclerView mList = CarUiUtils.requireViewByRefId(customList, R.id.list);
446         mList.setLayoutManager(new LinearLayoutManager(mContext));
447         mList.setAdapter(adapter);
448         mBuilder.setView(customList);
449     }
450 
451     /**
452      * Set a list of items, which are supplied by the given {@link Cursor}, to be
453      * displayed in the dialog as the content, you will be notified of the
454      * selected item via the supplied listener.
455      *
456      * @param cursor      The {@link Cursor} to supply the list of items
457      * @param listener    The listener that will be called when an item is clicked.
458      * @param labelColumn The column name on the cursor containing the string to display
459      *                    in the label.
460      * @return This Builder object to allow for chaining of calls to set methods
461      */
setCursor(final Cursor cursor, final DialogInterface.OnClickListener listener, String labelColumn)462     public AlertDialogBuilder setCursor(final Cursor cursor,
463             final DialogInterface.OnClickListener listener,
464             String labelColumn) {
465         mBuilder.setCursor(cursor, listener, labelColumn);
466         mHasSingleChoiceBodyButton = true;
467         return this;
468     }
469 
470     /**
471      * Set a list of items to be displayed in the dialog as the content,
472      * you will be notified of the selected item via the supplied listener.
473      * This should be an array type, e.g. R.array.foo. The list will have
474      * a check mark displayed to the right of the text for each checked
475      * item. Clicking on an item in the list will not dismiss the dialog.
476      * Clicking on a button will dismiss the dialog.
477      *
478      * @param itemsId      the resource id of an array i.e. R.array.foo
479      * @param checkedItems specifies which items are checked. It should be null in which case no
480      *                     items are checked. If non null it must be exactly the same length as the
481      *                     array of
482      *                     items.
483      * @param listener     notified when an item on the list is clicked. The dialog will not be
484      *                     dismissed when an item is clicked. It will only be dismissed if clicked
485      *                     on a
486      *                     button, if no buttons are supplied it's up to the user to dismiss the
487      *                     dialog.
488      * @return This Builder object to allow for chaining of calls to set methods
489      */
setMultiChoiceItems(@rrayRes int itemsId, boolean[] checkedItems, final DialogInterface.OnMultiChoiceClickListener listener)490     public AlertDialogBuilder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
491             final DialogInterface.OnMultiChoiceClickListener listener) {
492         mBuilder.setMultiChoiceItems(itemsId, checkedItems, listener);
493         mHasSingleChoiceBodyButton = false;
494         return this;
495     }
496 
497     /**
498      * Set a list of items to be displayed in the dialog as the content,
499      * you will be notified of the selected item via the supplied listener.
500      * The list will have a check mark displayed to the right of the text
501      * for each checked item. Clicking on an item in the list will not
502      * dismiss the dialog. Clicking on a button will dismiss the dialog.
503      *
504      * @param items        the text of the items to be displayed in the list.
505      * @param checkedItems specifies which items are checked. It should be null in which case no
506      *                     items are checked. If non null it must be exactly the same length as the
507      *                     array of
508      *                     items.
509      * @param listener     notified when an item on the list is clicked. The dialog will not be
510      *                     dismissed when an item is clicked. It will only be dismissed if clicked
511      *                     on a
512      *                     button, if no buttons are supplied it's up to the user to dismiss the
513      *                     dialog.
514      * @return This Builder object to allow for chaining of calls to set methods
515      */
setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final DialogInterface.OnMultiChoiceClickListener listener)516     public AlertDialogBuilder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
517             final DialogInterface.OnMultiChoiceClickListener listener) {
518         mBuilder.setMultiChoiceItems(items, checkedItems, listener);
519         mHasSingleChoiceBodyButton = false;
520         return this;
521     }
522 
523     /**
524      * Set a list of items to be displayed in the dialog as the content,
525      * you will be notified of the selected item via the supplied listener.
526      * The list will have a check mark displayed to the right of the text
527      * for each checked item. Clicking on an item in the list will not
528      * dismiss the dialog. Clicking on a button will dismiss the dialog.
529      *
530      * @param cursor          the cursor used to provide the items.
531      * @param isCheckedColumn specifies the column name on the cursor to use to determine
532      *                        whether a checkbox is checked or not. It must return an integer value
533      *                        where 1
534      *                        means checked and 0 means unchecked.
535      * @param labelColumn     The column name on the cursor containing the string to display in the
536      *                        label.
537      * @param listener        notified when an item on the list is clicked. The dialog will not be
538      *                        dismissed when an item is clicked. It will only be dismissed if
539      *                        clicked on a
540      *                        button, if no buttons are supplied it's up to the user to dismiss the
541      *                        dialog.
542      * @return This Builder object to allow for chaining of calls to set methods
543      */
setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final DialogInterface.OnMultiChoiceClickListener listener)544     public AlertDialogBuilder setMultiChoiceItems(Cursor cursor, String isCheckedColumn,
545             String labelColumn,
546             final DialogInterface.OnMultiChoiceClickListener listener) {
547         mBuilder.setMultiChoiceItems(cursor, isCheckedColumn, labelColumn, listener);
548         mHasSingleChoiceBodyButton = false;
549         return this;
550     }
551 
552     /**
553      * Set a list of items to be displayed in the dialog as the content, you will be notified of
554      * the selected item via the supplied listener. This should be an array type i.e.
555      * R.array.foo The list will have a check mark displayed to the right of the text for the
556      * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
557      * button will dismiss the dialog.
558      *
559      * @param itemsId     the resource id of an array i.e. R.array.foo
560      * @param checkedItem specifies which item is checked. If -1 no items are checked.
561      * @param listener    notified when an item on the list is clicked. The dialog will not be
562      *                    dismissed when an item is clicked. It will only be dismissed if clicked on
563      *                    a
564      *                    button, if no buttons are supplied it's up to the user to dismiss the
565      *                    dialog.
566      * @return This Builder object to allow for chaining of calls to set methods
567      */
setSingleChoiceItems(@rrayRes int itemsId, int checkedItem, final DialogInterface.OnClickListener listener)568     public AlertDialogBuilder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
569             final DialogInterface.OnClickListener listener) {
570         mBuilder.setSingleChoiceItems(itemsId, checkedItem, listener);
571         mHasSingleChoiceBodyButton = true;
572         return this;
573     }
574 
575     /**
576      * Set a list of items to be displayed in the dialog as the content, you will be notified of
577      * the selected item via the supplied listener. The list will have a check mark displayed to
578      * the right of the text for the checked item. Clicking on an item in the list will not
579      * dismiss the dialog. Clicking on a button will dismiss the dialog.
580      *
581      * @param cursor      the cursor to retrieve the items from.
582      * @param checkedItem specifies which item is checked. If -1 no items are checked.
583      * @param labelColumn The column name on the cursor containing the string to display in the
584      *                    label.
585      * @param listener    notified when an item on the list is clicked. The dialog will not be
586      *                    dismissed when an item is clicked. It will only be dismissed if clicked on
587      *                    a
588      *                    button, if no buttons are supplied it's up to the user to dismiss the
589      *                    dialog.
590      * @return This Builder object to allow for chaining of calls to set methods
591      */
setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final DialogInterface.OnClickListener listener)592     public AlertDialogBuilder setSingleChoiceItems(Cursor cursor, int checkedItem,
593             String labelColumn,
594             final DialogInterface.OnClickListener listener) {
595         mBuilder.setSingleChoiceItems(cursor, checkedItem, labelColumn, listener);
596         mHasSingleChoiceBodyButton = true;
597         return this;
598     }
599 
600     /**
601      * Set a list of items to be displayed in the dialog as the content, you will be notified of
602      * the selected item via the supplied listener. The list will have a check mark displayed to
603      * the right of the text for the checked item. Clicking on an item in the list will not
604      * dismiss the dialog. Clicking on a button will dismiss the dialog.
605      *
606      * @param items       the items to be displayed.
607      * @param checkedItem specifies which item is checked. If -1 no items are checked.
608      * @param listener    notified when an item on the list is clicked. The dialog will not be
609      *                    dismissed when an item is clicked. It will only be dismissed if clicked on
610      *                    a
611      *                    button, if no buttons are supplied it's up to the user to dismiss the
612      *                    dialog.
613      * @return This Builder object to allow for chaining of calls to set methods
614      */
setSingleChoiceItems(CharSequence[] items, int checkedItem, final DialogInterface.OnClickListener listener)615     public AlertDialogBuilder setSingleChoiceItems(CharSequence[] items, int checkedItem,
616             final DialogInterface.OnClickListener listener) {
617         mBuilder.setSingleChoiceItems(items, checkedItem, listener);
618         mHasSingleChoiceBodyButton = true;
619         return this;
620     }
621 
622     /**
623      * This was not supposed to be in the Chassis API because it allows custom views.
624      *
625      * @deprecated Use {@link #setSingleChoiceItems(CarUiRadioButtonListItemAdapter,
626      * DialogInterface.OnClickListener)} instead.
627      */
628     @Deprecated
setSingleChoiceItems(ListAdapter adapter, int checkedItem, final DialogInterface.OnClickListener listener)629     public AlertDialogBuilder setSingleChoiceItems(ListAdapter adapter, int checkedItem,
630             final DialogInterface.OnClickListener listener) {
631         mBuilder.setSingleChoiceItems(adapter, checkedItem, listener);
632         mHasSingleChoiceBodyButton = true;
633         return this;
634     }
635 
636     /**
637      * Set a list of items to be displayed in the dialog as the content, you will be notified of
638      * the selected item via the supplied listener. The list will have a check mark displayed to
639      * the right of the text for the checked item. Clicking on an item in the list will not
640      * dismiss the dialog. Clicking on a button will dismiss the dialog.
641      *
642      * @param adapter  The {@link CarUiRadioButtonListItemAdapter} to supply the list of items
643      * @param listener notified when an item on the list is clicked. The dialog will not be
644      *                 dismissed when an item is clicked. It will only be dismissed if clicked on a
645      *                 button, if no buttons are supplied it's up to the user to dismiss the dialog.
646      * @return This Builder object to allow for chaining of calls to set methods
647      * @deprecated Use {@link #setSingleChoiceItems(CarUiRadioButtonListItemAdapter)} instead.
648      */
649     @Deprecated
setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter, final DialogInterface.OnClickListener listener)650     public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter,
651             final DialogInterface.OnClickListener listener) {
652         setCustomList(adapter);
653         mHasSingleChoiceBodyButton = false;
654         return this;
655     }
656 
657     /**
658      * Set a list of items to be displayed in the dialog as the content,The list will have a check
659      * mark displayed to the right of the text for the checked item. Clicking on an item in the list
660      * will not dismiss the dialog. Clicking on a button will dismiss the dialog.
661      *
662      * @param adapter The {@link CarUiRadioButtonListItemAdapter} to supply the list of items
663      *                dismissed when an item is clicked. It will only be dismissed if clicked on a
664      *                button, if no buttons are supplied it's up to the user to dismiss the dialog.
665      * @return This Builder object to allow for chaining of calls to set methods
666      */
setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter)667     public AlertDialogBuilder setSingleChoiceItems(CarUiRadioButtonListItemAdapter adapter) {
668         setCustomList(adapter);
669         mHasSingleChoiceBodyButton = false;
670         return this;
671     }
672 
673     /**
674      * Sets a listener to be invoked when an item in the list is selected.
675      *
676      * @param listener the listener to be invoked
677      * @return this Builder object to allow for chaining of calls to set methods
678      * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
679      */
setOnItemSelectedListener( final AdapterView.OnItemSelectedListener listener)680     public AlertDialogBuilder setOnItemSelectedListener(
681             final AdapterView.OnItemSelectedListener listener) {
682         mBuilder.setOnItemSelectedListener(listener);
683         mHasSingleChoiceBodyButton = true;
684         return this;
685     }
686 
687     /**
688      * Sets a custom edit text box within the alert dialog.
689      *
690      * @param prompt              the string that will be set on the edit text view
691      * @param textChangedListener textWatcher whose methods are called whenever this TextView's text
692      *                            changes {@link null} otherwise.
693      * @param inputFilters        list of input filters, {@link null} if no filter is needed
694      * @param inputType           See {@link EditText#setInputType(int)}, except
695      *                            {@link android.text.InputType#TYPE_NULL} will not be set.
696      * @return this Builder object to allow for chaining of calls to set methods
697      */
setEditBox(String prompt, TextWatcher textChangedListener, InputFilter[] inputFilters, int inputType)698     public AlertDialogBuilder setEditBox(String prompt, TextWatcher textChangedListener,
699             InputFilter[] inputFilters, int inputType) {
700         View contentView = LayoutInflater.from(mContext).inflate(
701                 R.layout.car_ui_alert_dialog_edit_text, null);
702 
703         mCarUiEditText = CarUiUtils.requireViewByRefId(contentView, R.id.textbox);
704         mCarUiEditText.setText(prompt);
705 
706         if (textChangedListener != null) {
707             mCarUiEditText.addTextChangedListener(textChangedListener);
708         }
709 
710         if (inputFilters != null) {
711             mCarUiEditText.setFilters(inputFilters);
712         }
713 
714         if (inputType != 0) {
715             mCarUiEditText.setInputType(inputType);
716         }
717 
718         mBuilder.setView(contentView);
719         return this;
720     }
721 
722     /**
723      * Sets a custom edit text box within the alert dialog.
724      *
725      * @param prompt              the string that will be set on the edit text view
726      * @param textChangedListener textWatcher whose methods are called whenever this TextView's text
727      *                            changes {@link null} otherwise.
728      * @param inputFilters        list of input filters, {@link null} if no filter is needed
729      * @return this Builder object to allow for chaining of calls to set methods
730      */
setEditBox(String prompt, TextWatcher textChangedListener, InputFilter[] inputFilters)731     public AlertDialogBuilder setEditBox(String prompt, TextWatcher textChangedListener,
732             InputFilter[] inputFilters) {
733         return setEditBox(prompt, textChangedListener, inputFilters, 0);
734     }
735 
736     /**
737      * Sets the title and desc related to the dialog within the IMS templates.
738      *
739      * @param title title to be set.
740      * @param desc  description related to the dialog.
741      * @return this Builder object to allow for chaining of calls to set methods
742      */
setEditTextTitleAndDescForWideScreen(String title, String desc)743     public AlertDialogBuilder setEditTextTitleAndDescForWideScreen(String title, String desc) {
744         mWideScreenTitle = title;
745         mWideScreenTitleDesc = desc;
746 
747         return this;
748     }
749 
750     /**
751      * By default, the AlertDialogBuilder will just display the static text in the content area of
752      * widescreen IME provided by {@link #setEditTextTitleAndDescForWideScreen(String, String)}. To
753      * display the text typed by the user in the description set this to true.
754      *
755      * @return this Builder object to allow for chaining of calls to set methods
756      */
setAutoDescUpdateForWidescreen(boolean autoUpdateDesc)757     public AlertDialogBuilder setAutoDescUpdateForWidescreen(boolean autoUpdateDesc) {
758         if (autoUpdateDesc) {
759             mCarUiEditText.addTextChangedListener(mTextWatcherWideScreen);
760         } else {
761             mCarUiEditText.removeTextChangedListener(mTextWatcherWideScreen);
762         }
763         return this;
764     }
765 
766     /**
767      * By default, the AlertDialogBuilder may add a "Dismiss" button if you don't provide
768      * a positive/negative/neutral button. This is so that the dialog is still dismissible
769      * using the rotary controller. If however, you add buttons that can close the dialog via
770      * {@link #setAdapter(CarUiListItemAdapter)} or a similar method, then you may wish to
771      * suppress the addition of the dismiss button, which this method allows for.
772      *
773      * @param allowDismissButton If true, a "Dismiss" button may be added to the dialog.
774      *                           If false, it will never be added.
775      * @return this Builder object to allow for chaining of calls to set methods
776      */
setAllowDismissButton(boolean allowDismissButton)777     public AlertDialogBuilder setAllowDismissButton(boolean allowDismissButton) {
778         mAllowDismissButton = allowDismissButton;
779         return this;
780     }
781 
782     /** Final steps common to both {@link #create()} and {@link #show()} */
prepareDialog()783     private void prepareDialog() {
784         View customTitle = LayoutInflater.from(mContext).inflate(
785                 R.layout.car_ui_alert_dialog_title_with_subtitle, null);
786 
787         TextView mTitleView = CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_title);
788         TextView mSubtitleView =
789                 CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_subtitle);
790         mSubtitleView.setMovementMethod(LinkMovementMethod.getInstance());
791         ImageView mIconView = CarUiUtils.requireViewByRefId(customTitle, R.id.car_ui_alert_icon);
792 
793         mTitleView.setText(mTitle);
794         mTitleView.setVisibility(TextUtils.isEmpty(mTitle) ? View.GONE : View.VISIBLE);
795         mSubtitleView.setText(mSubtitle);
796         mSubtitleView.setVisibility(TextUtils.isEmpty(mSubtitle) ? View.GONE : View.VISIBLE);
797         mIconView.setImageDrawable(mIcon);
798         mIconView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
799         if (mIconTinted) {
800             mIconView.setImageTintList(
801                     mContext.getColorStateList(R.color.car_ui_dialog_icon_color));
802         }
803         mBuilder.setCustomTitle(customTitle);
804 
805         if (!mAllowDismissButton && !mHasSingleChoiceBodyButton
806                 && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet) {
807             throw new RuntimeException(
808                     "The dialog must have at least one button to disable the dismiss button");
809         }
810         if (mContext.getResources().getBoolean(R.bool.car_ui_alert_dialog_force_dismiss_button)
811                 && !mNeutralButtonSet && !mNegativeButtonSet && !mPositiveButtonSet
812                 && mAllowDismissButton) {
813             String mDefaultButtonText = mContext.getString(
814                     R.string.car_ui_alert_dialog_default_button);
815             mBuilder.setNegativeButton(mDefaultButtonText, (dialog, which) -> {
816             });
817         }
818     }
819 
820     /**
821      * Creates an {@link AlertDialog} with the arguments supplied to this
822      * builder.
823      * <p>
824      * Calling this method does not display the dialog. If no additional
825      * processing is needed, {@link #show()} may be called instead to both
826      * create and display the dialog.
827      */
create()828     public AlertDialog create() {
829         prepareDialog();
830         AlertDialog alertDialog = mBuilder.create();
831 
832         // Put a FocusParkingView at the end of dialog window to prevent rotary controller
833         // wrap-around. Android will focus on the first view automatically when the dialog is shown,
834         // and we want it to focus on the title instead of the FocusParkingView, so we put the
835         // FocusParkingView at the end of dialog window.
836         mRoot = (ViewGroup) alertDialog.getWindow().getDecorView().getRootView();
837         FocusParkingView fpv = new FocusParkingView(mContext);
838         mRoot.addView(fpv);
839 
840         // apply window insets listener to know when IME is visible so we can set title and desc.
841         mRoot.setOnApplyWindowInsetsListener(mOnApplyWindowInsetsListener);
842         setOnDismissListener(mOnDismissListener);
843 
844         return alertDialog;
845     }
846 
847     /**
848      * Creates an {@link AlertDialog} with the arguments supplied to this
849      * builder and immediately displays the dialog.
850      */
show()851     public AlertDialog show() {
852         AlertDialog alertDialog = create();
853         alertDialog.show();
854         return alertDialog;
855     }
856 }
857