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