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