1 /*
2  * Copyright (C) 2007 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.preference;
18 
19 
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.SharedPreferences;
25 import android.content.res.TypedArray;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.text.TextUtils;
31 import android.util.AttributeSet;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.Window;
35 import android.view.WindowManager;
36 import android.widget.TextView;
37 
38 /**
39  * A base class for {@link Preference} objects that are
40  * dialog-based. These preferences will, when clicked, open a dialog showing the
41  * actual preference controls.
42  *
43  * @attr ref android.R.styleable#DialogPreference_dialogTitle
44  * @attr ref android.R.styleable#DialogPreference_dialogMessage
45  * @attr ref android.R.styleable#DialogPreference_dialogIcon
46  * @attr ref android.R.styleable#DialogPreference_dialogLayout
47  * @attr ref android.R.styleable#DialogPreference_positiveButtonText
48  * @attr ref android.R.styleable#DialogPreference_negativeButtonText
49  */
50 public abstract class DialogPreference extends Preference implements
51         DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
52         PreferenceManager.OnActivityDestroyListener {
53     private AlertDialog.Builder mBuilder;
54 
55     private CharSequence mDialogTitle;
56     private CharSequence mDialogMessage;
57     private Drawable mDialogIcon;
58     private CharSequence mPositiveButtonText;
59     private CharSequence mNegativeButtonText;
60     private int mDialogLayoutResId;
61 
62     /** The dialog, if it is showing. */
63     private Dialog mDialog;
64 
65     /** Which button was clicked. */
66     private int mWhichButtonClicked;
67 
DialogPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)68     public DialogPreference(
69             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
70         super(context, attrs, defStyleAttr, defStyleRes);
71 
72         final TypedArray a = context.obtainStyledAttributes(attrs,
73                 com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes);
74         mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle);
75         if (mDialogTitle == null) {
76             // Fallback on the regular title of the preference
77             // (the one that is seen in the list)
78             mDialogTitle = getTitle();
79         }
80         mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage);
81         mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon);
82         mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText);
83         mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText);
84         mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout,
85                 mDialogLayoutResId);
86         a.recycle();
87     }
88 
DialogPreference(Context context, AttributeSet attrs, int defStyleAttr)89     public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
90         this(context, attrs, defStyleAttr, 0);
91     }
92 
DialogPreference(Context context, AttributeSet attrs)93     public DialogPreference(Context context, AttributeSet attrs) {
94         this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
95     }
96 
DialogPreference(Context context)97     public DialogPreference(Context context) {
98         this(context, null);
99     }
100 
101     /**
102      * Sets the title of the dialog. This will be shown on subsequent dialogs.
103      *
104      * @param dialogTitle The title.
105      */
setDialogTitle(CharSequence dialogTitle)106     public void setDialogTitle(CharSequence dialogTitle) {
107         mDialogTitle = dialogTitle;
108     }
109 
110     /**
111      * @see #setDialogTitle(CharSequence)
112      * @param dialogTitleResId The dialog title as a resource.
113      */
setDialogTitle(int dialogTitleResId)114     public void setDialogTitle(int dialogTitleResId) {
115         setDialogTitle(getContext().getString(dialogTitleResId));
116     }
117 
118     /**
119      * Returns the title to be shown on subsequent dialogs.
120      * @return The title.
121      */
getDialogTitle()122     public CharSequence getDialogTitle() {
123         return mDialogTitle;
124     }
125 
126     /**
127      * Sets the message of the dialog. This will be shown on subsequent dialogs.
128      * <p>
129      * This message forms the content View of the dialog and conflicts with
130      * list-based dialogs, for example. If setting a custom View on a dialog via
131      * {@link #setDialogLayoutResource(int)}, include a text View with ID
132      * {@link android.R.id#message} and it will be populated with this message.
133      *
134      * @param dialogMessage The message.
135      */
setDialogMessage(CharSequence dialogMessage)136     public void setDialogMessage(CharSequence dialogMessage) {
137         mDialogMessage = dialogMessage;
138     }
139 
140     /**
141      * @see #setDialogMessage(CharSequence)
142      * @param dialogMessageResId The dialog message as a resource.
143      */
setDialogMessage(int dialogMessageResId)144     public void setDialogMessage(int dialogMessageResId) {
145         setDialogMessage(getContext().getString(dialogMessageResId));
146     }
147 
148     /**
149      * Returns the message to be shown on subsequent dialogs.
150      * @return The message.
151      */
getDialogMessage()152     public CharSequence getDialogMessage() {
153         return mDialogMessage;
154     }
155 
156     /**
157      * Sets the icon of the dialog. This will be shown on subsequent dialogs.
158      *
159      * @param dialogIcon The icon, as a {@link Drawable}.
160      */
setDialogIcon(Drawable dialogIcon)161     public void setDialogIcon(Drawable dialogIcon) {
162         mDialogIcon = dialogIcon;
163     }
164 
165     /**
166      * Sets the icon (resource ID) of the dialog. This will be shown on
167      * subsequent dialogs.
168      *
169      * @param dialogIconRes The icon, as a resource ID.
170      */
setDialogIcon(int dialogIconRes)171     public void setDialogIcon(int dialogIconRes) {
172         mDialogIcon = getContext().getDrawable(dialogIconRes);
173     }
174 
175     /**
176      * Returns the icon to be shown on subsequent dialogs.
177      * @return The icon, as a {@link Drawable}.
178      */
getDialogIcon()179     public Drawable getDialogIcon() {
180         return mDialogIcon;
181     }
182 
183     /**
184      * Sets the text of the positive button of the dialog. This will be shown on
185      * subsequent dialogs.
186      *
187      * @param positiveButtonText The text of the positive button.
188      */
setPositiveButtonText(CharSequence positiveButtonText)189     public void setPositiveButtonText(CharSequence positiveButtonText) {
190         mPositiveButtonText = positiveButtonText;
191     }
192 
193     /**
194      * @see #setPositiveButtonText(CharSequence)
195      * @param positiveButtonTextResId The positive button text as a resource.
196      */
setPositiveButtonText(int positiveButtonTextResId)197     public void setPositiveButtonText(int positiveButtonTextResId) {
198         setPositiveButtonText(getContext().getString(positiveButtonTextResId));
199     }
200 
201     /**
202      * Returns the text of the positive button to be shown on subsequent
203      * dialogs.
204      *
205      * @return The text of the positive button.
206      */
getPositiveButtonText()207     public CharSequence getPositiveButtonText() {
208         return mPositiveButtonText;
209     }
210 
211     /**
212      * Sets the text of the negative button of the dialog. This will be shown on
213      * subsequent dialogs.
214      *
215      * @param negativeButtonText The text of the negative button.
216      */
setNegativeButtonText(CharSequence negativeButtonText)217     public void setNegativeButtonText(CharSequence negativeButtonText) {
218         mNegativeButtonText = negativeButtonText;
219     }
220 
221     /**
222      * @see #setNegativeButtonText(CharSequence)
223      * @param negativeButtonTextResId The negative button text as a resource.
224      */
setNegativeButtonText(int negativeButtonTextResId)225     public void setNegativeButtonText(int negativeButtonTextResId) {
226         setNegativeButtonText(getContext().getString(negativeButtonTextResId));
227     }
228 
229     /**
230      * Returns the text of the negative button to be shown on subsequent
231      * dialogs.
232      *
233      * @return The text of the negative button.
234      */
getNegativeButtonText()235     public CharSequence getNegativeButtonText() {
236         return mNegativeButtonText;
237     }
238 
239     /**
240      * Sets the layout resource that is inflated as the {@link View} to be shown
241      * as the content View of subsequent dialogs.
242      *
243      * @param dialogLayoutResId The layout resource ID to be inflated.
244      * @see #setDialogMessage(CharSequence)
245      */
setDialogLayoutResource(int dialogLayoutResId)246     public void setDialogLayoutResource(int dialogLayoutResId) {
247         mDialogLayoutResId = dialogLayoutResId;
248     }
249 
250     /**
251      * Returns the layout resource that is used as the content View for
252      * subsequent dialogs.
253      *
254      * @return The layout resource.
255      */
getDialogLayoutResource()256     public int getDialogLayoutResource() {
257         return mDialogLayoutResId;
258     }
259 
260     /**
261      * Prepares the dialog builder to be shown when the preference is clicked.
262      * Use this to set custom properties on the dialog.
263      * <p>
264      * Do not {@link AlertDialog.Builder#create()} or
265      * {@link AlertDialog.Builder#show()}.
266      */
onPrepareDialogBuilder(AlertDialog.Builder builder)267     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
268     }
269 
270     @Override
onClick()271     protected void onClick() {
272         if (mDialog != null && mDialog.isShowing()) return;
273 
274         showDialog(null);
275     }
276 
277     /**
278      * Shows the dialog associated with this Preference. This is normally initiated
279      * automatically on clicking on the preference. Call this method if you need to
280      * show the dialog on some other event.
281      *
282      * @param state Optional instance state to restore on the dialog
283      */
showDialog(Bundle state)284     protected void showDialog(Bundle state) {
285         Context context = getContext();
286 
287         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
288 
289         mBuilder = new AlertDialog.Builder(context)
290             .setTitle(mDialogTitle)
291             .setIcon(mDialogIcon)
292             .setPositiveButton(mPositiveButtonText, this)
293             .setNegativeButton(mNegativeButtonText, this);
294 
295         View contentView = onCreateDialogView();
296         if (contentView != null) {
297             onBindDialogView(contentView);
298             mBuilder.setView(contentView);
299         } else {
300             mBuilder.setMessage(mDialogMessage);
301         }
302 
303         onPrepareDialogBuilder(mBuilder);
304 
305         getPreferenceManager().registerOnActivityDestroyListener(this);
306 
307         // Create the dialog
308         final Dialog dialog = mDialog = mBuilder.create();
309         if (state != null) {
310             dialog.onRestoreInstanceState(state);
311         }
312         if (needInputMethod()) {
313             requestInputMethod(dialog);
314         }
315         dialog.setOnDismissListener(this);
316         dialog.show();
317     }
318 
319     /**
320      * Returns whether the preference needs to display a soft input method when the dialog
321      * is displayed. Default is false. Subclasses should override this method if they need
322      * the soft input method brought up automatically.
323      * @hide
324      */
needInputMethod()325     protected boolean needInputMethod() {
326         return false;
327     }
328 
329     /**
330      * Sets the required flags on the dialog window to enable input method window to show up.
331      */
requestInputMethod(Dialog dialog)332     private void requestInputMethod(Dialog dialog) {
333         Window window = dialog.getWindow();
334         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
335     }
336 
337     /**
338      * Creates the content view for the dialog (if a custom content view is
339      * required). By default, it inflates the dialog layout resource if it is
340      * set.
341      *
342      * @return The content View for the dialog.
343      * @see #setLayoutResource(int)
344      */
onCreateDialogView()345     protected View onCreateDialogView() {
346         if (mDialogLayoutResId == 0) {
347             return null;
348         }
349 
350         LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext());
351         return inflater.inflate(mDialogLayoutResId, null);
352     }
353 
354     /**
355      * Binds views in the content View of the dialog to data.
356      * <p>
357      * Make sure to call through to the superclass implementation.
358      *
359      * @param view The content View of the dialog, if it is custom.
360      */
onBindDialogView(View view)361     protected void onBindDialogView(View view) {
362         View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
363 
364         if (dialogMessageView != null) {
365             final CharSequence message = getDialogMessage();
366             int newVisibility = View.GONE;
367 
368             if (!TextUtils.isEmpty(message)) {
369                 if (dialogMessageView instanceof TextView) {
370                     ((TextView) dialogMessageView).setText(message);
371                 }
372 
373                 newVisibility = View.VISIBLE;
374             }
375 
376             if (dialogMessageView.getVisibility() != newVisibility) {
377                 dialogMessageView.setVisibility(newVisibility);
378             }
379         }
380     }
381 
onClick(DialogInterface dialog, int which)382     public void onClick(DialogInterface dialog, int which) {
383         mWhichButtonClicked = which;
384     }
385 
onDismiss(DialogInterface dialog)386     public void onDismiss(DialogInterface dialog) {
387 
388         getPreferenceManager().unregisterOnActivityDestroyListener(this);
389 
390         mDialog = null;
391         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
392     }
393 
394     /**
395      * Called when the dialog is dismissed and should be used to save data to
396      * the {@link SharedPreferences}.
397      *
398      * @param positiveResult Whether the positive button was clicked (true), or
399      *            the negative button was clicked or the dialog was canceled (false).
400      */
onDialogClosed(boolean positiveResult)401     protected void onDialogClosed(boolean positiveResult) {
402     }
403 
404     /**
405      * Gets the dialog that is shown by this preference.
406      *
407      * @return The dialog, or null if a dialog is not being shown.
408      */
getDialog()409     public Dialog getDialog() {
410         return mDialog;
411     }
412 
413     /**
414      * {@inheritDoc}
415      */
onActivityDestroy()416     public void onActivityDestroy() {
417 
418         if (mDialog == null || !mDialog.isShowing()) {
419             return;
420         }
421 
422         mDialog.dismiss();
423     }
424 
425     @Override
onSaveInstanceState()426     protected Parcelable onSaveInstanceState() {
427         final Parcelable superState = super.onSaveInstanceState();
428         if (mDialog == null || !mDialog.isShowing()) {
429             return superState;
430         }
431 
432         final SavedState myState = new SavedState(superState);
433         myState.isDialogShowing = true;
434         myState.dialogBundle = mDialog.onSaveInstanceState();
435         return myState;
436     }
437 
438     @Override
onRestoreInstanceState(Parcelable state)439     protected void onRestoreInstanceState(Parcelable state) {
440         if (state == null || !state.getClass().equals(SavedState.class)) {
441             // Didn't save state for us in onSaveInstanceState
442             super.onRestoreInstanceState(state);
443             return;
444         }
445 
446         SavedState myState = (SavedState) state;
447         super.onRestoreInstanceState(myState.getSuperState());
448         if (myState.isDialogShowing) {
449             showDialog(myState.dialogBundle);
450         }
451     }
452 
453     private static class SavedState extends BaseSavedState {
454         boolean isDialogShowing;
455         Bundle dialogBundle;
456 
SavedState(Parcel source)457         public SavedState(Parcel source) {
458             super(source);
459             isDialogShowing = source.readInt() == 1;
460             dialogBundle = source.readBundle();
461         }
462 
463         @Override
writeToParcel(Parcel dest, int flags)464         public void writeToParcel(Parcel dest, int flags) {
465             super.writeToParcel(dest, flags);
466             dest.writeInt(isDialogShowing ? 1 : 0);
467             dest.writeBundle(dialogBundle);
468         }
469 
SavedState(Parcelable superState)470         public SavedState(Parcelable superState) {
471             super(superState);
472         }
473 
474         public static final Parcelable.Creator<SavedState> CREATOR =
475                 new Parcelable.Creator<SavedState>() {
476             public SavedState createFromParcel(Parcel in) {
477                 return new SavedState(in);
478             }
479 
480             public SavedState[] newArray(int size) {
481                 return new SavedState[size];
482             }
483         };
484     }
485 
486 }
487