• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 androidx.preference;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.Fragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.graphics.Bitmap;
28 import android.graphics.Canvas;
29 import android.graphics.drawable.BitmapDrawable;
30 import android.graphics.drawable.Drawable;
31 import android.os.Bundle;
32 import android.text.TextUtils;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.Window;
36 import android.view.WindowManager;
37 import android.widget.TextView;
38 
39 import androidx.annotation.LayoutRes;
40 import androidx.annotation.NonNull;
41 import androidx.annotation.RestrictTo;
42 
43 /**
44  * Abstract base class which presents a dialog associated with a
45  * {@link androidx.preference.DialogPreference}. Since the preference object may
46  * not be available during fragment re-creation, the necessary information for displaying the dialog
47  * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
48  * instance state. Custom subclasses should also follow this pattern.
49  */
50 public abstract class PreferenceDialogFragment extends DialogFragment implements
51         DialogInterface.OnClickListener {
52 
53     protected static final String ARG_KEY = "key";
54 
55     private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
56     private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
57     private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
58     private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
59     private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
60     private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
61 
62     private DialogPreference mPreference;
63 
64     private CharSequence mDialogTitle;
65     private CharSequence mPositiveButtonText;
66     private CharSequence mNegativeButtonText;
67     private CharSequence mDialogMessage;
68     private @LayoutRes int mDialogLayoutRes;
69 
70     private BitmapDrawable mDialogIcon;
71 
72     /** Which button was clicked. */
73     private int mWhichButtonClicked;
74 
75     @Override
onCreate(Bundle savedInstanceState)76     public void onCreate(Bundle savedInstanceState) {
77         super.onCreate(savedInstanceState);
78 
79         final Fragment rawFragment = getTargetFragment();
80         if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
81             throw new IllegalStateException("Target fragment must implement TargetFragment"
82                     + " interface");
83         }
84 
85         final DialogPreference.TargetFragment fragment =
86                 (DialogPreference.TargetFragment) rawFragment;
87 
88         final String key = getArguments().getString(ARG_KEY);
89         if (savedInstanceState == null) {
90             mPreference = (DialogPreference) fragment.findPreference(key);
91             mDialogTitle = mPreference.getDialogTitle();
92             mPositiveButtonText = mPreference.getPositiveButtonText();
93             mNegativeButtonText = mPreference.getNegativeButtonText();
94             mDialogMessage = mPreference.getDialogMessage();
95             mDialogLayoutRes = mPreference.getDialogLayoutResource();
96 
97             final Drawable icon = mPreference.getDialogIcon();
98             if (icon == null || icon instanceof BitmapDrawable) {
99                 mDialogIcon = (BitmapDrawable) icon;
100             } else {
101                 final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
102                         icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
103                 final Canvas canvas = new Canvas(bitmap);
104                 icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
105                 icon.draw(canvas);
106                 mDialogIcon = new BitmapDrawable(getResources(), bitmap);
107             }
108         } else {
109             mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
110             mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
111             mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
112             mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
113             mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
114             final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
115             if (bitmap != null) {
116                 mDialogIcon = new BitmapDrawable(getResources(), bitmap);
117             }
118         }
119     }
120 
121     @Override
onSaveInstanceState(@onNull Bundle outState)122     public void onSaveInstanceState(@NonNull Bundle outState) {
123         super.onSaveInstanceState(outState);
124 
125         outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
126         outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
127         outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
128         outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
129         outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
130         if (mDialogIcon != null) {
131             outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
132         }
133     }
134 
135     @NonNull
136     @Override
onCreateDialog(Bundle savedInstanceState)137     public Dialog onCreateDialog(Bundle savedInstanceState) {
138         final Context context = getActivity();
139         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
140 
141         final AlertDialog.Builder builder = new AlertDialog.Builder(context)
142                 .setTitle(mDialogTitle)
143                 .setIcon(mDialogIcon)
144                 .setPositiveButton(mPositiveButtonText, this)
145                 .setNegativeButton(mNegativeButtonText, this);
146 
147         View contentView = onCreateDialogView(context);
148         if (contentView != null) {
149             onBindDialogView(contentView);
150             builder.setView(contentView);
151         } else {
152             builder.setMessage(mDialogMessage);
153         }
154 
155         onPrepareDialogBuilder(builder);
156 
157         // Create the dialog
158         final Dialog dialog = builder.create();
159         if (needInputMethod()) {
160             requestInputMethod(dialog);
161         }
162 
163         return dialog;
164     }
165 
166     /**
167      * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
168      * been called on the {@link PreferenceFragment} which launched this dialog.
169      *
170      * @return The {@link DialogPreference} associated with this
171      * dialog.
172      */
getPreference()173     public DialogPreference getPreference() {
174         if (mPreference == null) {
175             final String key = getArguments().getString(ARG_KEY);
176             final DialogPreference.TargetFragment fragment =
177                     (DialogPreference.TargetFragment) getTargetFragment();
178             mPreference = (DialogPreference) fragment.findPreference(key);
179         }
180         return mPreference;
181     }
182 
183     /**
184      * Prepares the dialog builder to be shown when the preference is clicked.
185      * Use this to set custom properties on the dialog.
186      * <p>
187      * Do not {@link AlertDialog.Builder#create()} or
188      * {@link AlertDialog.Builder#show()}.
189      */
onPrepareDialogBuilder(AlertDialog.Builder builder)190     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {}
191 
192     /**
193      * Returns whether the preference needs to display a soft input method when the dialog
194      * is displayed. Default is false. Subclasses should override this method if they need
195      * the soft input method brought up automatically.
196      *
197      * <p>Note: If your application targets P or above, ensure your subclass manually requests
198      * focus (ideally in {@link #onBindDialogView(View)}) for the input field in order to
199      * correctly attach the input method to the field.
200      *
201      * @hide
202      */
203     @RestrictTo(LIBRARY_GROUP)
needInputMethod()204     protected boolean needInputMethod() {
205         return false;
206     }
207 
208     /**
209      * Sets the required flags on the dialog window to enable input method window to show up.
210      */
requestInputMethod(Dialog dialog)211     private void requestInputMethod(Dialog dialog) {
212         Window window = dialog.getWindow();
213         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
214     }
215 
216     /**
217      * Creates the content view for the dialog (if a custom content view is
218      * required). By default, it inflates the dialog layout resource if it is
219      * set.
220      *
221      * @return The content View for the dialog.
222      * @see DialogPreference#setLayoutResource(int)
223      */
onCreateDialogView(Context context)224     protected View onCreateDialogView(Context context) {
225         final int resId = mDialogLayoutRes;
226         if (resId == 0) {
227             return null;
228         }
229 
230         LayoutInflater inflater = LayoutInflater.from(context);
231         return inflater.inflate(resId, null);
232     }
233 
234     /**
235      * Binds views in the content View of the dialog to data.
236      * <p>
237      * Make sure to call through to the superclass implementation.
238      *
239      * @param view The content View of the dialog, if it is custom.
240      */
onBindDialogView(View view)241     protected void onBindDialogView(View view) {
242         View dialogMessageView = view.findViewById(android.R.id.message);
243 
244         if (dialogMessageView != null) {
245             final CharSequence message = mDialogMessage;
246             int newVisibility = View.GONE;
247 
248             if (!TextUtils.isEmpty(message)) {
249                 if (dialogMessageView instanceof TextView) {
250                     ((TextView) dialogMessageView).setText(message);
251                 }
252 
253                 newVisibility = View.VISIBLE;
254             }
255 
256             if (dialogMessageView.getVisibility() != newVisibility) {
257                 dialogMessageView.setVisibility(newVisibility);
258             }
259         }
260     }
261 
262     @Override
onClick(DialogInterface dialog, int which)263     public void onClick(DialogInterface dialog, int which) {
264         mWhichButtonClicked = which;
265     }
266 
267     @Override
onDismiss(DialogInterface dialog)268     public void onDismiss(DialogInterface dialog) {
269         super.onDismiss(dialog);
270         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
271     }
272 
onDialogClosed(boolean positiveResult)273     public abstract void onDialogClosed(boolean positiveResult);
274 }
275