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