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 17 package com.android.car.settings.common; 18 19 import android.app.Dialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.os.Bundle; 23 import android.os.Parcelable; 24 import android.text.TextUtils; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.annotation.StringRes; 29 30 import com.android.car.ui.AlertDialogBuilder; 31 import com.android.car.ui.preference.CarUiDialogFragment; 32 33 /** 34 * Common dialog that can be used across the settings app to ask the user to confirm their desired 35 * action. 36 */ 37 public class ConfirmationDialogFragment extends CarUiDialogFragment { 38 39 /** Builder to help construct {@link ConfirmationDialogFragment}. */ 40 public static class Builder { 41 42 private final Context mContext; 43 private Bundle mArgs; 44 private String mTitle; 45 private String mMessage; 46 private String mPosLabel; 47 private String mNegLabel; 48 private String mNeuLabel; 49 private ConfirmListener mConfirmListener; 50 private RejectListener mRejectListener; 51 private NeutralListener mNeutralListener; 52 private DismissListener mDismissListener; 53 Builder(Context context)54 public Builder(Context context) { 55 mContext = context; 56 } 57 58 /** Sets the title. */ setTitle(String title)59 public Builder setTitle(String title) { 60 mTitle = title; 61 return this; 62 } 63 64 /** Sets the title. */ setTitle(@tringRes int title)65 public Builder setTitle(@StringRes int title) { 66 mTitle = mContext.getString(title); 67 return this; 68 } 69 70 /** Sets the message. */ setMessage(String message)71 public Builder setMessage(String message) { 72 mMessage = message; 73 return this; 74 } 75 76 /** Sets the message. */ setMessage(@tringRes int message)77 public Builder setMessage(@StringRes int message) { 78 mMessage = mContext.getString(message); 79 return this; 80 } 81 82 /** Sets the positive button label. */ setPositiveButton(String label, ConfirmListener confirmListener)83 public Builder setPositiveButton(String label, ConfirmListener confirmListener) { 84 mPosLabel = label; 85 mConfirmListener = confirmListener; 86 return this; 87 } 88 89 /** Sets the positive button label. */ setPositiveButton(@tringRes int label, ConfirmListener confirmListener)90 public Builder setPositiveButton(@StringRes int label, ConfirmListener confirmListener) { 91 mPosLabel = mContext.getString(label); 92 mConfirmListener = confirmListener; 93 return this; 94 } 95 96 /** Sets the negative button label. */ setNegativeButton(String label, RejectListener rejectListener)97 public Builder setNegativeButton(String label, RejectListener rejectListener) { 98 mNegLabel = label; 99 mRejectListener = rejectListener; 100 return this; 101 } 102 103 /** Sets the negative button label. */ setNegativeButton(@tringRes int label, RejectListener rejectListener)104 public Builder setNegativeButton(@StringRes int label, RejectListener rejectListener) { 105 mNegLabel = mContext.getString(label); 106 mRejectListener = rejectListener; 107 return this; 108 } 109 110 /** Sets the neutral button label. */ setNeutralButton(@tringRes int label, NeutralListener neutralListener)111 public Builder setNeutralButton(@StringRes int label, NeutralListener neutralListener) { 112 mNeuLabel = mContext.getString(label); 113 mNeutralListener = neutralListener; 114 return this; 115 } 116 117 /** Sets the listener for dialog dismiss. */ setDismissListener(DismissListener dismissListener)118 public Builder setDismissListener(DismissListener dismissListener) { 119 mDismissListener = dismissListener; 120 return this; 121 } 122 123 /** Adds an argument string to the argument bundle. */ addArgumentString(String argumentKey, String argument)124 public Builder addArgumentString(String argumentKey, String argument) { 125 if (mArgs == null) { 126 mArgs = new Bundle(); 127 } 128 mArgs.putString(argumentKey, argument); 129 return this; 130 } 131 132 /** Adds an argument long to the argument bundle. */ addArgumentLong(String argumentKey, long argument)133 public Builder addArgumentLong(String argumentKey, long argument) { 134 if (mArgs == null) { 135 mArgs = new Bundle(); 136 } 137 mArgs.putLong(argumentKey, argument); 138 return this; 139 } 140 141 /** Adds an argument boolean to the argument bundle. */ addArgumentBoolean(String argumentKey, boolean argument)142 public Builder addArgumentBoolean(String argumentKey, boolean argument) { 143 if (mArgs == null) { 144 mArgs = new Bundle(); 145 } 146 mArgs.putBoolean(argumentKey, argument); 147 return this; 148 } 149 150 /** Adds an argument Parcelable to the argument bundle. */ addArgumentParcelable(String argumentKey, Parcelable argument)151 public Builder addArgumentParcelable(String argumentKey, Parcelable argument) { 152 if (mArgs == null) { 153 mArgs = new Bundle(); 154 } 155 mArgs.putParcelable(argumentKey, argument); 156 return this; 157 } 158 159 /** Constructs the {@link ConfirmationDialogFragment}. */ build()160 public ConfirmationDialogFragment build() { 161 return ConfirmationDialogFragment.init(this); 162 } 163 } 164 165 /** Identifier used to launch the dialog fragment. */ 166 public static final String TAG = "ConfirmationDialogFragment"; 167 168 // Argument keys are prefixed with TAG in order to reduce the changes of collision with user 169 // provided arguments. 170 private static final String ALL_ARGUMENTS_KEY = TAG + "_all_arguments"; 171 private static final String ARGUMENTS_KEY = TAG + "_arguments"; 172 private static final String TITLE_KEY = TAG + "_title"; 173 private static final String MESSAGE_KEY = TAG + "_message"; 174 private static final String POSITIVE_KEY = TAG + "_positive"; 175 private static final String NEGATIVE_KEY = TAG + "_negative"; 176 private static final String NEUTRAL_KEY = TAG + "_neutral"; 177 178 private String mTitle; 179 private String mMessage; 180 private String mPosLabel; 181 private String mNegLabel; 182 private String mNeuLabel; 183 private ConfirmListener mConfirmListener; 184 private RejectListener mRejectListener; 185 private NeutralListener mNeutralListener; 186 private DismissListener mDismissListener; 187 188 /** Constructs the dialog fragment from the arguments provided in the {@link Builder} */ init(Builder builder)189 private static ConfirmationDialogFragment init(Builder builder) { 190 ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment(); 191 Bundle args = new Bundle(); 192 args.putBundle(ARGUMENTS_KEY, builder.mArgs); 193 args.putString(TITLE_KEY, builder.mTitle); 194 args.putString(MESSAGE_KEY, builder.mMessage); 195 args.putString(POSITIVE_KEY, builder.mPosLabel); 196 args.putString(NEGATIVE_KEY, builder.mNegLabel); 197 args.putString(NEUTRAL_KEY, builder.mNeuLabel); 198 dialogFragment.setArguments(args); 199 dialogFragment.setConfirmListener(builder.mConfirmListener); 200 dialogFragment.setRejectListener(builder.mRejectListener); 201 dialogFragment.setNeutralListener(builder.mNeutralListener); 202 dialogFragment.setDismissListener(builder.mDismissListener); 203 return dialogFragment; 204 } 205 206 /** 207 * Since it is possible for the listeners to be unregistered on configuration change, provide a 208 * way to reattach the listeners. 209 */ resetListeners(@ullable ConfirmationDialogFragment dialogFragment, @Nullable ConfirmListener confirmListener, @Nullable RejectListener rejectListener, @Nullable NeutralListener neutralListener)210 public static void resetListeners(@Nullable ConfirmationDialogFragment dialogFragment, 211 @Nullable ConfirmListener confirmListener, 212 @Nullable RejectListener rejectListener, 213 @Nullable NeutralListener neutralListener) { 214 if (dialogFragment != null) { 215 dialogFragment.setConfirmListener(confirmListener); 216 dialogFragment.setRejectListener(rejectListener); 217 dialogFragment.setNeutralListener(neutralListener); 218 } 219 } 220 221 /** Sets the listeners which listens for the dialog dismissed */ setDismissListener(DismissListener dismissListener)222 public void setDismissListener(DismissListener dismissListener) { 223 mDismissListener = dismissListener; 224 } 225 226 /** Gets the listener which listens for the dialog dismissed */ 227 @Nullable getDismissListener()228 public DismissListener getDismissListener() { 229 return mDismissListener; 230 } 231 232 /** Sets the listener which listens to a click on the positive button. */ setConfirmListener(ConfirmListener confirmListener)233 private void setConfirmListener(ConfirmListener confirmListener) { 234 mConfirmListener = confirmListener; 235 } 236 237 /** Gets the listener which listens to a click on the positive button */ 238 @Nullable getConfirmListener()239 public ConfirmListener getConfirmListener() { 240 return mConfirmListener; 241 } 242 243 /** Sets the listener which listens to a click on the negative button. */ setRejectListener(RejectListener rejectListener)244 private void setRejectListener(RejectListener rejectListener) { 245 mRejectListener = rejectListener; 246 } 247 248 /** Gets the listener which listens to a click on the negative button. */ 249 @Nullable getRejectListener()250 public RejectListener getRejectListener() { 251 return mRejectListener; 252 } 253 254 /** Sets the listener which listens to a click on the neutral button. */ setNeutralListener(NeutralListener neutralListener)255 private void setNeutralListener(NeutralListener neutralListener) { 256 mNeutralListener = neutralListener; 257 } 258 259 /** Gets the listener which listens to a click on the neutral button. */ 260 @Nullable getNeutralListener()261 public NeutralListener getNeutralListener() { 262 return mNeutralListener; 263 } 264 265 @Override onCreate(@ullable Bundle savedInstanceState)266 public void onCreate(@Nullable Bundle savedInstanceState) { 267 super.onCreate(savedInstanceState); 268 269 Bundle args = getArguments(); 270 if (savedInstanceState != null) { 271 args = savedInstanceState.getBundle(ALL_ARGUMENTS_KEY); 272 } 273 274 if (args != null) { 275 mTitle = getArguments().getString(TITLE_KEY); 276 mMessage = getArguments().getString(MESSAGE_KEY); 277 mPosLabel = getArguments().getString(POSITIVE_KEY); 278 mNegLabel = getArguments().getString(NEGATIVE_KEY); 279 mNeuLabel = getArguments().getString(NEUTRAL_KEY); 280 } 281 } 282 283 @Override onSaveInstanceState(@onNull Bundle outState)284 public void onSaveInstanceState(@NonNull Bundle outState) { 285 super.onSaveInstanceState(outState); 286 outState.putBundle(ALL_ARGUMENTS_KEY, getArguments()); 287 } 288 289 @NonNull 290 @Override onCreateDialog(@ullable Bundle savedInstanceState)291 public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 292 AlertDialogBuilder builder = new AlertDialogBuilder(getContext()); 293 if (!TextUtils.isEmpty(mTitle)) { 294 builder.setTitle(mTitle); 295 } 296 if (!TextUtils.isEmpty(mMessage)) { 297 builder.setMessage(mMessage); 298 } 299 if (!TextUtils.isEmpty(mPosLabel)) { 300 builder.setPositiveButton(mPosLabel, this); 301 } 302 if (!TextUtils.isEmpty(mNegLabel)) { 303 builder.setNegativeButton(mNegLabel, this); 304 } 305 if (!TextUtils.isEmpty(mNeuLabel)) { 306 builder.setNeutralButton(mNeuLabel, this); 307 } 308 return builder.create(); 309 } 310 311 @Override onDialogClosed(boolean positiveResult)312 protected void onDialogClosed(boolean positiveResult) { 313 if (mDismissListener != null) { 314 mDismissListener.onDismiss(getArguments().getBundle(ARGUMENTS_KEY)); 315 } 316 } 317 318 @Override onClick(DialogInterface dialog, int which)319 public void onClick(DialogInterface dialog, int which) { 320 if (which == DialogInterface.BUTTON_POSITIVE) { 321 if (mConfirmListener != null) { 322 mConfirmListener.onConfirm(getArguments().getBundle(ARGUMENTS_KEY)); 323 } 324 } else if (which == DialogInterface.BUTTON_NEGATIVE) { 325 if (mRejectListener != null) { 326 mRejectListener.onReject(getArguments().getBundle(ARGUMENTS_KEY)); 327 } 328 } else if (which == DialogInterface.BUTTON_NEUTRAL) { 329 if (mNeutralListener != null) { 330 mNeutralListener.onNeutral(getArguments().getBundle(ARGUMENTS_KEY)); 331 } 332 } 333 } 334 335 /** Listens to the confirmation action. */ 336 public interface ConfirmListener { 337 /** 338 * Defines the action to take on confirm. The bundle will contain the arguments added when 339 * constructing the dialog through with {@link Builder#addArgumentString(String, String)}. 340 */ onConfirm(@ullable Bundle arguments)341 void onConfirm(@Nullable Bundle arguments); 342 } 343 344 /** Listens to the rejection action. */ 345 public interface RejectListener { 346 /** 347 * Defines the action to take on reject. The bundle will contain the arguments added when 348 * constructing the dialog through with {@link Builder#addArgumentString(String, String)}. 349 */ onReject(@ullable Bundle arguments)350 void onReject(@Nullable Bundle arguments); 351 } 352 353 /** Listens to the neutral button action. */ 354 public interface NeutralListener { 355 /** 356 * Defines the action to take on neutral button click. The bundle will contain the arguments 357 * added when constructing the dialog through with 358 * {@link Builder#addArgumentString(String, String)}. 359 */ onNeutral(@ullable Bundle arguments)360 void onNeutral(@Nullable Bundle arguments); 361 } 362 363 /** Listens to the dismiss action. */ 364 public interface DismissListener { 365 /** 366 * Defines the action to take when the dialog is closed. The bundle will contain the 367 * arguments added when constructing the dialog through with 368 * {@link Builder#addArgumentString(String, String)}. 369 */ onDismiss(@ullable Bundle arguments)370 void onDismiss(@Nullable Bundle arguments); 371 } 372 } 373