1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.connecteddevice.audiosharing; 18 19 import android.content.Context; 20 import android.content.DialogInterface; 21 import android.graphics.drawable.Drawable; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.widget.Button; 25 import android.widget.ImageView; 26 import android.widget.TextView; 27 28 import androidx.annotation.DrawableRes; 29 import androidx.annotation.NonNull; 30 import androidx.annotation.StringRes; 31 import androidx.appcompat.app.AlertDialog; 32 import androidx.recyclerview.widget.LinearLayoutManager; 33 import androidx.recyclerview.widget.RecyclerView; 34 35 import com.android.settings.R; 36 37 import java.util.List; 38 39 import javax.annotation.CheckReturnValue; 40 41 public class AudioSharingDialogFactory { 42 private static final String TAG = "AudioSharingDialogFactory"; 43 44 /** 45 * Initializes a builder for the dialog to be shown for audio sharing. 46 * 47 * @param context The {@link Context} that will be used to create the dialog. 48 * @return A configurable builder for the dialog. 49 */ 50 @NonNull newBuilder(@onNull Context context)51 public static AudioSharingDialogFactory.DialogBuilder newBuilder(@NonNull Context context) { 52 return new AudioSharingDialogFactory.DialogBuilder(context); 53 } 54 55 /** 56 * Updates the title of the dialog custom title. 57 * 58 * @param dialog The dialog to update 59 * @param titleText The text to be used for the title.. 60 */ updateTitle(@onNull AlertDialog dialog, @NonNull CharSequence titleText)61 public static void updateTitle(@NonNull AlertDialog dialog, 62 @NonNull CharSequence titleText) { 63 TextView title = dialog.findViewById(R.id.title_text); 64 if (title != null) { 65 title.setText(titleText); 66 title.setVisibility(View.VISIBLE); 67 } 68 } 69 70 /** 71 * Updates the custom message of the dialog custom body. 72 * 73 * @param dialog The dialog to update 74 * @param message The text to be used for the custom message body. 75 */ updateCustomMessage(@onNull AlertDialog dialog, @NonNull CharSequence message)76 public static void updateCustomMessage(@NonNull AlertDialog dialog, 77 @NonNull CharSequence message) { 78 TextView subTitle = dialog.findViewById(R.id.description_text); 79 if (subTitle != null) { 80 subTitle.setText(message); 81 subTitle.setVisibility(View.VISIBLE); 82 } 83 } 84 85 /** 86 * Updates the custom device actions of the dialog custom body. 87 * 88 * @param dialog The dialog to update 89 * @param deviceItems device items to build dialog actions. 90 */ updateCustomDeviceActions( @onNull AlertDialog dialog, @NonNull List<AudioSharingDeviceItem> deviceItems)91 public static void updateCustomDeviceActions( 92 @NonNull AlertDialog dialog, @NonNull List<AudioSharingDeviceItem> deviceItems) { 93 RecyclerView recyclerView = dialog.findViewById(R.id.device_btn_list); 94 if (recyclerView != null && recyclerView.getVisibility() == View.VISIBLE) { 95 RecyclerView.Adapter adapter = recyclerView.getAdapter(); 96 if (adapter instanceof AudioSharingDeviceAdapter) { 97 ((AudioSharingDeviceAdapter) adapter).updateItems(deviceItems); 98 } 99 } 100 } 101 102 /** Builder class with configurable options for the dialog to be shown for audio sharing. */ 103 public static class DialogBuilder { 104 private Context mContext; 105 private AlertDialog.Builder mBuilder; 106 private View mCustomTitle; 107 private View mCustomBody; 108 private boolean mIsCustomBodyEnabled; 109 110 /** 111 * Private constructor for the dialog builder class. Should not be invoked directly; 112 * instead, use {@link AudioSharingDialogFactory#newBuilder(Context)}. 113 * 114 * @param context The {@link Context} that will be used to create the dialog. 115 */ DialogBuilder(@onNull Context context)116 private DialogBuilder(@NonNull Context context) { 117 mContext = context; 118 mBuilder = new AlertDialog.Builder(context); 119 LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext()); 120 mCustomTitle = 121 inflater.inflate(R.layout.dialog_custom_title_audio_sharing, /* root= */ null); 122 mCustomBody = 123 inflater.inflate(R.layout.dialog_custom_body_audio_sharing, /* parent= */ null); 124 } 125 126 /** 127 * Sets title of the dialog custom title. 128 * 129 * @param titleRes Resource ID of the string to be used for the dialog title. 130 * @return This builder. 131 */ 132 @NonNull setTitle(@tringRes int titleRes)133 public AudioSharingDialogFactory.DialogBuilder setTitle(@StringRes int titleRes) { 134 TextView title = mCustomTitle.findViewById(R.id.title_text); 135 title.setText(titleRes); 136 return this; 137 } 138 139 /** 140 * Sets title of the dialog custom title. 141 * 142 * @param titleText The text to be used for the title. 143 * @return This builder. 144 */ 145 @NonNull setTitle(@onNull CharSequence titleText)146 public AudioSharingDialogFactory.DialogBuilder setTitle(@NonNull CharSequence titleText) { 147 TextView title = mCustomTitle.findViewById(R.id.title_text); 148 title.setText(titleText); 149 return this; 150 } 151 152 /** 153 * Sets the title icon of the dialog custom title. 154 * 155 * @param iconRes The text to be used for the title. 156 * @return This builder. 157 */ 158 @NonNull setTitleIcon(@rawableRes int iconRes)159 public AudioSharingDialogFactory.DialogBuilder setTitleIcon(@DrawableRes int iconRes) { 160 ImageView icon = mCustomTitle.findViewById(R.id.title_icon); 161 icon.setImageResource(iconRes); 162 return this; 163 } 164 165 /** 166 * Sets the message body of the dialog. 167 * 168 * @param messageRes Resource ID of the string to be used for the message body. 169 * @return This builder. 170 */ 171 @NonNull setMessage(@tringRes int messageRes)172 public AudioSharingDialogFactory.DialogBuilder setMessage(@StringRes int messageRes) { 173 mBuilder.setMessage(messageRes); 174 return this; 175 } 176 177 /** 178 * Sets the message body of the dialog. 179 * 180 * @param message The text to be used for the message body. 181 * @return This builder. 182 */ 183 @NonNull setMessage(@onNull CharSequence message)184 public AudioSharingDialogFactory.DialogBuilder setMessage(@NonNull CharSequence message) { 185 mBuilder.setMessage(message); 186 return this; 187 } 188 189 /** Whether to use custom body. */ 190 @NonNull setIsCustomBodyEnabled( boolean isCustomBodyEnabled)191 public AudioSharingDialogFactory.DialogBuilder setIsCustomBodyEnabled( 192 boolean isCustomBodyEnabled) { 193 mIsCustomBodyEnabled = isCustomBodyEnabled; 194 return this; 195 } 196 197 /** 198 * Sets the custom image of the dialog custom body. 199 * 200 * @param iconRes The iconRes to be used for the image. 201 * @return This builder. 202 */ 203 @NonNull setCustomImage(@rawableRes int iconRes)204 public AudioSharingDialogFactory.DialogBuilder setCustomImage(@DrawableRes int iconRes) { 205 ImageView image = mCustomBody.findViewById(R.id.description_image); 206 image.setImageResource(iconRes); 207 image.setVisibility(View.VISIBLE); 208 return this; 209 } 210 211 /** 212 * Sets the custom image of the dialog custom body. 213 * 214 * @param drawable The drawable to be used for the image. 215 * @return This builder. 216 */ 217 @NonNull setCustomImage(Drawable drawable)218 public AudioSharingDialogFactory.DialogBuilder setCustomImage(Drawable drawable) { 219 ImageView image = mCustomBody.findViewById(R.id.description_image); 220 image.setImageDrawable(drawable); 221 image.setVisibility(View.VISIBLE); 222 return this; 223 } 224 225 /** 226 * Sets the custom message of the dialog custom body. 227 * 228 * @param messageRes Resource ID of the string to be used for the message body. 229 * @return This builder. 230 */ 231 @NonNull setCustomMessage(@tringRes int messageRes)232 public AudioSharingDialogFactory.DialogBuilder setCustomMessage(@StringRes int messageRes) { 233 TextView subTitle = mCustomBody.findViewById(R.id.description_text); 234 subTitle.setText(messageRes); 235 subTitle.setVisibility(View.VISIBLE); 236 return this; 237 } 238 239 /** 240 * Sets the custom message of the dialog custom body. 241 * 242 * @param message The text to be used for the custom message body. 243 * @return This builder. 244 */ 245 @NonNull setCustomMessage( @onNull CharSequence message)246 public AudioSharingDialogFactory.DialogBuilder setCustomMessage( 247 @NonNull CharSequence message) { 248 TextView subTitle = mCustomBody.findViewById(R.id.description_text); 249 subTitle.setText(message); 250 subTitle.setVisibility(View.VISIBLE); 251 return this; 252 } 253 254 /** 255 * Sets the custom message below image. 256 * 257 * @param messageRes Resource ID of the string to be used for the message body. 258 * @return This builder. 259 */ 260 @NonNull setCustomMessage2( @tringRes int messageRes)261 public AudioSharingDialogFactory.DialogBuilder setCustomMessage2( 262 @StringRes int messageRes) { 263 TextView subTitle = mCustomBody.findViewById(R.id.description_text_2); 264 subTitle.setText(messageRes); 265 subTitle.setVisibility(View.VISIBLE); 266 return this; 267 } 268 269 /** 270 * Sets the custom device actions of the dialog custom body. 271 * 272 * @param adapter The adapter for device items to build dialog actions. 273 * @return This builder. 274 */ 275 @NonNull setCustomDeviceActions( @onNull AudioSharingDeviceAdapter adapter)276 public AudioSharingDialogFactory.DialogBuilder setCustomDeviceActions( 277 @NonNull AudioSharingDeviceAdapter adapter) { 278 RecyclerView recyclerView = mCustomBody.findViewById(R.id.device_btn_list); 279 recyclerView.setAdapter(adapter); 280 recyclerView.setLayoutManager( 281 new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); 282 recyclerView.setVisibility(View.VISIBLE); 283 return this; 284 } 285 286 /** 287 * Sets the positive button label and listener for the dialog. 288 * 289 * @param labelRes Resource ID of the string to be used for the positive button label. 290 * @param listener The listener to be invoked when the positive button is pressed. 291 * @return This builder. 292 */ 293 @NonNull setPositiveButton( @tringRes int labelRes, @NonNull DialogInterface.OnClickListener listener)294 public AudioSharingDialogFactory.DialogBuilder setPositiveButton( 295 @StringRes int labelRes, @NonNull DialogInterface.OnClickListener listener) { 296 mBuilder.setPositiveButton(labelRes, listener); 297 return this; 298 } 299 300 /** 301 * Sets the positive button label and listener for the dialog. 302 * 303 * @param label The text to be used for the positive button label. 304 * @param listener The listener to be invoked when the positive button is pressed. 305 * @return This builder. 306 */ 307 @NonNull setPositiveButton( @onNull CharSequence label, @NonNull DialogInterface.OnClickListener listener)308 public AudioSharingDialogFactory.DialogBuilder setPositiveButton( 309 @NonNull CharSequence label, @NonNull DialogInterface.OnClickListener listener) { 310 mBuilder.setPositiveButton(label, listener); 311 return this; 312 } 313 314 /** 315 * Sets the custom positive button label and listener for the dialog custom body. 316 * 317 * @param labelRes Resource ID of the string to be used for the positive button label. 318 * @param listener The listener to be invoked when the positive button is pressed. 319 * @return This builder. 320 */ 321 @NonNull setCustomPositiveButton( @tringRes int labelRes, @NonNull View.OnClickListener listener)322 public AudioSharingDialogFactory.DialogBuilder setCustomPositiveButton( 323 @StringRes int labelRes, @NonNull View.OnClickListener listener) { 324 Button positiveBtn = mCustomBody.findViewById(R.id.positive_btn); 325 positiveBtn.setText(labelRes); 326 positiveBtn.setOnClickListener(listener); 327 positiveBtn.setVisibility(View.VISIBLE); 328 return this; 329 } 330 331 /** 332 * Sets the custom positive button label and listener for the dialog custom body. 333 * 334 * @param label The text to be used for the positive button label. 335 * @param listener The listener to be invoked when the positive button is pressed. 336 * @return This builder. 337 */ 338 @NonNull setCustomPositiveButton( @onNull CharSequence label, @NonNull View.OnClickListener listener)339 public AudioSharingDialogFactory.DialogBuilder setCustomPositiveButton( 340 @NonNull CharSequence label, @NonNull View.OnClickListener listener) { 341 Button positiveBtn = mCustomBody.findViewById(R.id.positive_btn); 342 positiveBtn.setText(label); 343 positiveBtn.setOnClickListener(listener); 344 positiveBtn.setVisibility(View.VISIBLE); 345 return this; 346 } 347 348 /** 349 * Sets the negative button label and listener for the dialog. 350 * 351 * @param labelRes Resource ID of the string to be used for the negative button label. 352 * @param listener The listener to be invoked when the negative button is pressed. 353 * @return This builder. 354 */ 355 @NonNull setNegativeButton( @tringRes int labelRes, @NonNull DialogInterface.OnClickListener listener)356 public AudioSharingDialogFactory.DialogBuilder setNegativeButton( 357 @StringRes int labelRes, @NonNull DialogInterface.OnClickListener listener) { 358 mBuilder.setNegativeButton(labelRes, listener); 359 return this; 360 } 361 362 /** 363 * Sets the negative button label and listener for the dialog. 364 * 365 * @param label The text to be used for the negative button label. 366 * @param listener The listener to be invoked when the negative button is pressed. 367 * @return This builder. 368 */ 369 @NonNull setNegativeButton( @onNull CharSequence label, @NonNull DialogInterface.OnClickListener listener)370 public AudioSharingDialogFactory.DialogBuilder setNegativeButton( 371 @NonNull CharSequence label, @NonNull DialogInterface.OnClickListener listener) { 372 mBuilder.setNegativeButton(label, listener); 373 return this; 374 } 375 376 /** 377 * Sets the custom negative button label and listener for the dialog custom body. 378 * 379 * @param labelRes Resource ID of the string to be used for the negative button label. 380 * @param listener The listener to be invoked when the negative button is pressed. 381 * @return This builder. 382 */ 383 @NonNull setCustomNegativeButton( @tringRes int labelRes, @NonNull View.OnClickListener listener)384 public AudioSharingDialogFactory.DialogBuilder setCustomNegativeButton( 385 @StringRes int labelRes, @NonNull View.OnClickListener listener) { 386 Button negativeBtn = mCustomBody.findViewById(R.id.negative_btn); 387 negativeBtn.setText(labelRes); 388 negativeBtn.setOnClickListener(listener); 389 negativeBtn.setVisibility(View.VISIBLE); 390 return this; 391 } 392 393 /** 394 * Sets the custom negative button label and listener for the dialog custom body. 395 * 396 * @param label The text to be used for the negative button label. 397 * @param listener The listener to be invoked when the negative button is pressed. 398 * @return This builder. 399 */ 400 @NonNull setCustomNegativeButton( @onNull CharSequence label, @NonNull View.OnClickListener listener)401 public AudioSharingDialogFactory.DialogBuilder setCustomNegativeButton( 402 @NonNull CharSequence label, @NonNull View.OnClickListener listener) { 403 Button negativeBtn = mCustomBody.findViewById(R.id.negative_btn); 404 negativeBtn.setText(label); 405 negativeBtn.setOnClickListener(listener); 406 negativeBtn.setVisibility(View.VISIBLE); 407 return this; 408 } 409 410 /** 411 * Builds a dialog with the current configs. 412 * 413 * @return The dialog to be shown for audio sharing. 414 */ 415 @NonNull 416 @CheckReturnValue build()417 public AlertDialog build() { 418 if (mIsCustomBodyEnabled) { 419 mBuilder.setView(mCustomBody); 420 } 421 final AlertDialog dialog = 422 mBuilder.setCustomTitle(mCustomTitle).setCancelable(false).create(); 423 dialog.setCanceledOnTouchOutside(false); 424 return dialog; 425 } 426 } 427 AudioSharingDialogFactory()428 private AudioSharingDialogFactory() {} 429 } 430