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