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