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