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 ConfirmListener mConfirmListener;
49         private RejectListener mRejectListener;
50 
Builder(Context context)51         public Builder(Context context) {
52             mContext = context;
53         }
54 
55         /** Sets the title. */
setTitle(String title)56         public Builder setTitle(String title) {
57             mTitle = title;
58             return this;
59         }
60 
61         /** Sets the title. */
setTitle(@tringRes int title)62         public Builder setTitle(@StringRes int title) {
63             mTitle = mContext.getString(title);
64             return this;
65         }
66 
67         /** Sets the message. */
setMessage(String message)68         public Builder setMessage(String message) {
69             mMessage = message;
70             return this;
71         }
72 
73         /** Sets the message. */
setMessage(@tringRes int message)74         public Builder setMessage(@StringRes int message) {
75             mMessage = mContext.getString(message);
76             return this;
77         }
78 
79         /** Sets the positive button label. */
setPositiveButton(String label, ConfirmListener confirmListener)80         public Builder setPositiveButton(String label, ConfirmListener confirmListener) {
81             mPosLabel = label;
82             mConfirmListener = confirmListener;
83             return this;
84         }
85 
86         /** Sets the positive button label. */
setPositiveButton(@tringRes int label, ConfirmListener confirmListener)87         public Builder setPositiveButton(@StringRes int label, ConfirmListener confirmListener) {
88             mPosLabel = mContext.getString(label);
89             mConfirmListener = confirmListener;
90             return this;
91         }
92 
93         /** Sets the negative button label. */
setNegativeButton(String label, RejectListener rejectListener)94         public Builder setNegativeButton(String label, RejectListener rejectListener) {
95             mNegLabel = label;
96             mRejectListener = rejectListener;
97             return this;
98         }
99 
100         /** Sets the negative button label. */
setNegativeButton(@tringRes int label, RejectListener rejectListener)101         public Builder setNegativeButton(@StringRes int label, RejectListener rejectListener) {
102             mNegLabel = mContext.getString(label);
103             mRejectListener = rejectListener;
104             return this;
105         }
106 
107         /** Adds an argument string to the argument bundle. */
addArgumentString(String argumentKey, String argument)108         public Builder addArgumentString(String argumentKey, String argument) {
109             if (mArgs == null) {
110                 mArgs = new Bundle();
111             }
112             mArgs.putString(argumentKey, argument);
113             return this;
114         }
115 
116         /** Adds an argument boolean to the argument bundle. */
addArgumentBoolean(String argumentKey, boolean argument)117         public Builder addArgumentBoolean(String argumentKey, boolean argument) {
118             if (mArgs == null) {
119                 mArgs = new Bundle();
120             }
121             mArgs.putBoolean(argumentKey, argument);
122             return this;
123         }
124 
125         /** Adds an argument Parcelable to the argument bundle. */
addArgumentParcelable(String argumentKey, Parcelable argument)126         public Builder addArgumentParcelable(String argumentKey, Parcelable argument) {
127             if (mArgs == null) {
128                 mArgs = new Bundle();
129             }
130             mArgs.putParcelable(argumentKey, argument);
131             return this;
132         }
133 
134         /** Constructs the {@link ConfirmationDialogFragment}. */
build()135         public ConfirmationDialogFragment build() {
136             return ConfirmationDialogFragment.init(this);
137         }
138     }
139 
140     /** Identifier used to launch the dialog fragment. */
141     public static final String TAG = "ConfirmationDialogFragment";
142 
143     // Argument keys are prefixed with TAG in order to reduce the changes of collision with user
144     // provided arguments.
145     private static final String ALL_ARGUMENTS_KEY = TAG + "_all_arguments";
146     private static final String ARGUMENTS_KEY = TAG + "_arguments";
147     private static final String TITLE_KEY = TAG + "_title";
148     private static final String MESSAGE_KEY = TAG + "_message";
149     private static final String POSITIVE_KEY = TAG + "_positive";
150     private static final String NEGATIVE_KEY = TAG + "_negative";
151 
152     private String mTitle;
153     private String mMessage;
154     private String mPosLabel;
155     private String mNegLabel;
156     private ConfirmListener mConfirmListener;
157     private RejectListener mRejectListener;
158 
159     /** Constructs the dialog fragment from the arguments provided in the {@link Builder} */
init(Builder builder)160     private static ConfirmationDialogFragment init(Builder builder) {
161         ConfirmationDialogFragment dialogFragment = new ConfirmationDialogFragment();
162         Bundle args = new Bundle();
163         args.putBundle(ARGUMENTS_KEY, builder.mArgs);
164         args.putString(TITLE_KEY, builder.mTitle);
165         args.putString(MESSAGE_KEY, builder.mMessage);
166         args.putString(POSITIVE_KEY, builder.mPosLabel);
167         args.putString(NEGATIVE_KEY, builder.mNegLabel);
168         dialogFragment.setArguments(args);
169         dialogFragment.setConfirmListener(builder.mConfirmListener);
170         dialogFragment.setRejectListener(builder.mRejectListener);
171         return dialogFragment;
172     }
173 
174     /**
175      * Since it is possible for the listeners to be unregistered on configuration change, provide a
176      * way to reattach the listeners.
177      */
resetListeners(@ullable ConfirmationDialogFragment dialogFragment, @Nullable ConfirmListener confirmListener, @Nullable RejectListener rejectListener)178     public static void resetListeners(@Nullable ConfirmationDialogFragment dialogFragment,
179             @Nullable ConfirmListener confirmListener, @Nullable RejectListener rejectListener) {
180         if (dialogFragment != null) {
181             dialogFragment.setConfirmListener(confirmListener);
182             dialogFragment.setRejectListener(rejectListener);
183         }
184     }
185 
186     /** Sets the listener which listens to a click on the positive button. */
setConfirmListener(ConfirmListener confirmListener)187     private void setConfirmListener(ConfirmListener confirmListener) {
188         mConfirmListener = confirmListener;
189     }
190 
191     /** Gets the listener which listens to a click on the positive button */
192     @Nullable
getConfirmListener()193     public ConfirmListener getConfirmListener() {
194         return mConfirmListener;
195     }
196 
197     /** Sets the listener which listens to a click on the negative button. */
setRejectListener(RejectListener rejectListener)198     private void setRejectListener(RejectListener rejectListener) {
199         mRejectListener = rejectListener;
200     }
201 
202     /** Gets the listener which listens to a click on the negative button. */
203     @Nullable
getRejectListener()204     public RejectListener getRejectListener() {
205         return mRejectListener;
206     }
207 
208     @Override
onCreate(@ullable Bundle savedInstanceState)209     public void onCreate(@Nullable Bundle savedInstanceState) {
210         super.onCreate(savedInstanceState);
211 
212         Bundle args = getArguments();
213         if (savedInstanceState != null) {
214             args = savedInstanceState.getBundle(ALL_ARGUMENTS_KEY);
215         }
216 
217         if (args != null) {
218             mTitle = getArguments().getString(TITLE_KEY);
219             mMessage = getArguments().getString(MESSAGE_KEY);
220             mPosLabel = getArguments().getString(POSITIVE_KEY);
221             mNegLabel = getArguments().getString(NEGATIVE_KEY);
222         }
223     }
224 
225     @Override
onSaveInstanceState(@onNull Bundle outState)226     public void onSaveInstanceState(@NonNull Bundle outState) {
227         super.onSaveInstanceState(outState);
228         outState.putBundle(ALL_ARGUMENTS_KEY, getArguments());
229     }
230 
231     @NonNull
232     @Override
onCreateDialog(@ullable Bundle savedInstanceState)233     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
234         AlertDialogBuilder builder = new AlertDialogBuilder(getContext());
235         if (!TextUtils.isEmpty(mTitle)) {
236             builder.setTitle(mTitle);
237         }
238         if (!TextUtils.isEmpty(mMessage)) {
239             builder.setMessage(mMessage);
240         }
241         if (!TextUtils.isEmpty(mPosLabel)) {
242             builder.setPositiveButton(mPosLabel, this);
243         }
244         if (!TextUtils.isEmpty(mNegLabel)) {
245             builder.setNegativeButton(mNegLabel, this);
246         }
247         return builder.create();
248     }
249 
250     @Override
onDialogClosed(boolean positiveResult)251     protected void onDialogClosed(boolean positiveResult) {
252     }
253 
254     @Override
onClick(DialogInterface dialog, int which)255     public void onClick(DialogInterface dialog, int which) {
256         if (which == DialogInterface.BUTTON_POSITIVE) {
257             if (mConfirmListener != null) {
258                 mConfirmListener.onConfirm(getArguments().getBundle(ARGUMENTS_KEY));
259             }
260         } else if (which == DialogInterface.BUTTON_NEGATIVE) {
261             if (mRejectListener != null) {
262                 mRejectListener.onReject(getArguments().getBundle(ARGUMENTS_KEY));
263             }
264         }
265     }
266 
267     /** Listens to the confirmation action. */
268     public interface ConfirmListener {
269         /**
270          * Defines the action to take on confirm. The bundle will contain the arguments added when
271          * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
272          */
onConfirm(@ullable Bundle arguments)273         void onConfirm(@Nullable Bundle arguments);
274     }
275 
276     /** Listens to the rejection action. */
277     public interface RejectListener {
278         /**
279          * Defines the action to take on reject. The bundle will contain the arguments added when
280          * constructing the dialog through with {@link Builder#addArgumentString(String, String)}.
281          */
onReject(@ullable Bundle arguments)282         void onReject(@Nullable Bundle arguments);
283     }
284 }
285