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