Home | History | Annotate | Download | only in preference
      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 
     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         if (mDialog != null && mDialog.isShowing()) return;
    265 
    266         showDialog(null);
    267     }
    268 
    269     /**
    270      * Shows the dialog associated with this Preference. This is normally initiated
    271      * automatically on clicking on the preference. Call this method if you need to
    272      * show the dialog on some other event.
    273      *
    274      * @param state Optional instance state to restore on the dialog
    275      */
    276     protected void showDialog(Bundle state) {
    277         Context context = getContext();
    278 
    279         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
    280 
    281         mBuilder = new AlertDialog.Builder(context)
    282             .setTitle(mDialogTitle)
    283             .setIcon(mDialogIcon)
    284             .setPositiveButton(mPositiveButtonText, this)
    285             .setNegativeButton(mNegativeButtonText, this);
    286 
    287         View contentView = onCreateDialogView();
    288         if (contentView != null) {
    289             onBindDialogView(contentView);
    290             mBuilder.setView(contentView);
    291         } else {
    292             mBuilder.setMessage(mDialogMessage);
    293         }
    294 
    295         onPrepareDialogBuilder(mBuilder);
    296 
    297         getPreferenceManager().registerOnActivityDestroyListener(this);
    298 
    299         // Create the dialog
    300         final Dialog dialog = mDialog = mBuilder.create();
    301         if (state != null) {
    302             dialog.onRestoreInstanceState(state);
    303         }
    304         if (needInputMethod()) {
    305             requestInputMethod(dialog);
    306         }
    307         dialog.setOnDismissListener(this);
    308         dialog.show();
    309     }
    310 
    311     /**
    312      * Returns whether the preference needs to display a soft input method when the dialog
    313      * is displayed. Default is false. Subclasses should override this method if they need
    314      * the soft input method brought up automatically.
    315      * @hide
    316      */
    317     protected boolean needInputMethod() {
    318         return false;
    319     }
    320 
    321     /**
    322      * Sets the required flags on the dialog window to enable input method window to show up.
    323      */
    324     private void requestInputMethod(Dialog dialog) {
    325         Window window = dialog.getWindow();
    326         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    327     }
    328 
    329     /**
    330      * Creates the content view for the dialog (if a custom content view is
    331      * required). By default, it inflates the dialog layout resource if it is
    332      * set.
    333      *
    334      * @return The content View for the dialog.
    335      * @see #setLayoutResource(int)
    336      */
    337     protected View onCreateDialogView() {
    338         if (mDialogLayoutResId == 0) {
    339             return null;
    340         }
    341 
    342         LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext());
    343         return inflater.inflate(mDialogLayoutResId, null);
    344     }
    345 
    346     /**
    347      * Binds views in the content View of the dialog to data.
    348      * <p>
    349      * Make sure to call through to the superclass implementation.
    350      *
    351      * @param view The content View of the dialog, if it is custom.
    352      */
    353     protected void onBindDialogView(View view) {
    354         View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
    355 
    356         if (dialogMessageView != null) {
    357             final CharSequence message = getDialogMessage();
    358             int newVisibility = View.GONE;
    359 
    360             if (!TextUtils.isEmpty(message)) {
    361                 if (dialogMessageView instanceof TextView) {
    362                     ((TextView) dialogMessageView).setText(message);
    363                 }
    364 
    365                 newVisibility = View.VISIBLE;
    366             }
    367 
    368             if (dialogMessageView.getVisibility() != newVisibility) {
    369                 dialogMessageView.setVisibility(newVisibility);
    370             }
    371         }
    372     }
    373 
    374     public void onClick(DialogInterface dialog, int which) {
    375         mWhichButtonClicked = which;
    376     }
    377 
    378     public void onDismiss(DialogInterface dialog) {
    379 
    380         getPreferenceManager().unregisterOnActivityDestroyListener(this);
    381 
    382         mDialog = null;
    383         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
    384     }
    385 
    386     /**
    387      * Called when the dialog is dismissed and should be used to save data to
    388      * the {@link SharedPreferences}.
    389      *
    390      * @param positiveResult Whether the positive button was clicked (true), or
    391      *            the negative button was clicked or the dialog was canceled (false).
    392      */
    393     protected void onDialogClosed(boolean positiveResult) {
    394     }
    395 
    396     /**
    397      * Gets the dialog that is shown by this preference.
    398      *
    399      * @return The dialog, or null if a dialog is not being shown.
    400      */
    401     public Dialog getDialog() {
    402         return mDialog;
    403     }
    404 
    405     /**
    406      * {@inheritDoc}
    407      */
    408     public void onActivityDestroy() {
    409 
    410         if (mDialog == null || !mDialog.isShowing()) {
    411             return;
    412         }
    413 
    414         mDialog.dismiss();
    415     }
    416 
    417     @Override
    418     protected Parcelable onSaveInstanceState() {
    419         final Parcelable superState = super.onSaveInstanceState();
    420         if (mDialog == null || !mDialog.isShowing()) {
    421             return superState;
    422         }
    423 
    424         final SavedState myState = new SavedState(superState);
    425         myState.isDialogShowing = true;
    426         myState.dialogBundle = mDialog.onSaveInstanceState();
    427         return myState;
    428     }
    429 
    430     @Override
    431     protected void onRestoreInstanceState(Parcelable state) {
    432         if (state == null || !state.getClass().equals(SavedState.class)) {
    433             // Didn't save state for us in onSaveInstanceState
    434             super.onRestoreInstanceState(state);
    435             return;
    436         }
    437 
    438         SavedState myState = (SavedState) state;
    439         super.onRestoreInstanceState(myState.getSuperState());
    440         if (myState.isDialogShowing) {
    441             showDialog(myState.dialogBundle);
    442         }
    443     }
    444 
    445     private static class SavedState extends BaseSavedState {
    446         boolean isDialogShowing;
    447         Bundle dialogBundle;
    448 
    449         public SavedState(Parcel source) {
    450             super(source);
    451             isDialogShowing = source.readInt() == 1;
    452             dialogBundle = source.readBundle();
    453         }
    454 
    455         @Override
    456         public void writeToParcel(Parcel dest, int flags) {
    457             super.writeToParcel(dest, flags);
    458             dest.writeInt(isDialogShowing ? 1 : 0);
    459             dest.writeBundle(dialogBundle);
    460         }
    461 
    462         public SavedState(Parcelable superState) {
    463             super(superState);
    464         }
    465 
    466         public static final Parcelable.Creator<SavedState> CREATOR =
    467                 new Parcelable.Creator<SavedState>() {
    468             public SavedState createFromParcel(Parcel in) {
    469                 return new SavedState(in);
    470             }
    471 
    472             public SavedState[] newArray(int size) {
    473                 return new SavedState[size];
    474             }
    475         };
    476     }
    477 
    478 }
    479