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