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