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