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(
     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 
     89     public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
     90         this(context, attrs, defStyleAttr, 0);
     91     }
     92 
     93     public DialogPreference(Context context, AttributeSet attrs) {
     94         this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
     95     }
     96 
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    267     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
    268     }
    269 
    270     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    382     public void onClick(DialogInterface dialog, int which) {
    383         mWhichButtonClicked = which;
    384     }
    385 
    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      */
    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      */
    409     public Dialog getDialog() {
    410         return mDialog;
    411     }
    412 
    413     /**
    414      * {@inheritDoc}
    415      */
    416     public void onActivityDestroy() {
    417 
    418         if (mDialog == null || !mDialog.isShowing()) {
    419             return;
    420         }
    421 
    422         mDialog.dismiss();
    423     }
    424 
    425     @Override
    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
    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 
    457         public SavedState(Parcel source) {
    458             super(source);
    459             isDialogShowing = source.readInt() == 1;
    460             dialogBundle = source.readBundle();
    461         }
    462 
    463         @Override
    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 
    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