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         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