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