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 android.support.v14.preference;
18 
19 import static android.support.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.support.annotation.LayoutRes;
33 import android.support.annotation.NonNull;
34 import android.support.annotation.RestrictTo;
35 import android.support.v7.preference.DialogPreference;
36 import android.text.TextUtils;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.Window;
40 import android.view.WindowManager;
41 import android.widget.TextView;
42 
43 /**
44  * Abstract base class which presents a dialog associated with a
45  * {@link android.support.v7.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     @Override
136     public @NonNull
onCreateDialog(Bundle savedInstanceState)137     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      * @hide
197      */
198     @RestrictTo(LIBRARY_GROUP)
needInputMethod()199     protected boolean needInputMethod() {
200         return false;
201     }
202 
203     /**
204      * Sets the required flags on the dialog window to enable input method window to show up.
205      */
requestInputMethod(Dialog dialog)206     private void requestInputMethod(Dialog dialog) {
207         Window window = dialog.getWindow();
208         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
209     }
210 
211     /**
212      * Creates the content view for the dialog (if a custom content view is
213      * required). By default, it inflates the dialog layout resource if it is
214      * set.
215      *
216      * @return The content View for the dialog.
217      * @see DialogPreference#setLayoutResource(int)
218      */
onCreateDialogView(Context context)219     protected View onCreateDialogView(Context context) {
220         final int resId = mDialogLayoutRes;
221         if (resId == 0) {
222             return null;
223         }
224 
225         LayoutInflater inflater = LayoutInflater.from(context);
226         return inflater.inflate(resId, null);
227     }
228 
229     /**
230      * Binds views in the content View of the dialog to data.
231      * <p>
232      * Make sure to call through to the superclass implementation.
233      *
234      * @param view The content View of the dialog, if it is custom.
235      */
onBindDialogView(View view)236     protected void onBindDialogView(View view) {
237         View dialogMessageView = view.findViewById(android.R.id.message);
238 
239         if (dialogMessageView != null) {
240             final CharSequence message = mDialogMessage;
241             int newVisibility = View.GONE;
242 
243             if (!TextUtils.isEmpty(message)) {
244                 if (dialogMessageView instanceof TextView) {
245                     ((TextView) dialogMessageView).setText(message);
246                 }
247 
248                 newVisibility = View.VISIBLE;
249             }
250 
251             if (dialogMessageView.getVisibility() != newVisibility) {
252                 dialogMessageView.setVisibility(newVisibility);
253             }
254         }
255     }
256 
257     @Override
onClick(DialogInterface dialog, int which)258     public void onClick(DialogInterface dialog, int which) {
259         mWhichButtonClicked = which;
260     }
261 
262     @Override
onDismiss(DialogInterface dialog)263     public void onDismiss(DialogInterface dialog) {
264         super.onDismiss(dialog);
265         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
266     }
267 
onDialogClosed(boolean positiveResult)268     public abstract void onDialogClosed(boolean positiveResult);
269 }
270