Home | History | Annotate | Download | only in preference
      1 /*
      2  * Copyright 2018 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 androidx.preference;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.app.Dialog;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.drawable.BitmapDrawable;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.Bundle;
     29 import android.text.TextUtils;
     30 import android.view.LayoutInflater;
     31 import android.view.View;
     32 import android.view.Window;
     33 import android.view.WindowManager;
     34 import android.widget.TextView;
     35 
     36 import androidx.annotation.LayoutRes;
     37 import androidx.annotation.NonNull;
     38 import androidx.annotation.RestrictTo;
     39 import androidx.appcompat.app.AlertDialog;
     40 import androidx.fragment.app.DialogFragment;
     41 import androidx.fragment.app.Fragment;
     42 
     43 /**
     44  * Abstract base class which presents a dialog associated with a
     45  * {@link androidx.preference.DialogPreference}. Since the preference object may
     46  * not be available during fragment re-creation, the necessary information for displaying the dialog
     47  * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
     48  * instance state. Custom subclasses should also follow this pattern.
     49  */
     50 public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements
     51         DialogInterface.OnClickListener {
     52 
     53     protected static final String ARG_KEY = "key";
     54 
     55     private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
     56     private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
     57     private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
     58     private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
     59     private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
     60     private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
     61 
     62     private DialogPreference mPreference;
     63 
     64     private CharSequence mDialogTitle;
     65     private CharSequence mPositiveButtonText;
     66     private CharSequence mNegativeButtonText;
     67     private CharSequence mDialogMessage;
     68     private @LayoutRes int mDialogLayoutRes;
     69 
     70     private BitmapDrawable mDialogIcon;
     71 
     72     /** Which button was clicked. */
     73     private int mWhichButtonClicked;
     74 
     75     @Override
     76     public void onCreate(Bundle savedInstanceState) {
     77         super.onCreate(savedInstanceState);
     78 
     79         final Fragment rawFragment = getTargetFragment();
     80         if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
     81             throw new IllegalStateException("Target fragment must implement TargetFragment" +
     82                     " interface");
     83         }
     84 
     85         final DialogPreference.TargetFragment fragment =
     86                 (DialogPreference.TargetFragment) rawFragment;
     87 
     88         final String key = getArguments().getString(ARG_KEY);
     89         if (savedInstanceState == null) {
     90             mPreference = (DialogPreference) fragment.findPreference(key);
     91             mDialogTitle = mPreference.getDialogTitle();
     92             mPositiveButtonText = mPreference.getPositiveButtonText();
     93             mNegativeButtonText = mPreference.getNegativeButtonText();
     94             mDialogMessage = mPreference.getDialogMessage();
     95             mDialogLayoutRes = mPreference.getDialogLayoutResource();
     96 
     97             final Drawable icon = mPreference.getDialogIcon();
     98             if (icon == null || icon instanceof BitmapDrawable) {
     99                 mDialogIcon = (BitmapDrawable) icon;
    100             } else {
    101                 final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
    102                         icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
    103                 final Canvas canvas = new Canvas(bitmap);
    104                 icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    105                 icon.draw(canvas);
    106                 mDialogIcon = new BitmapDrawable(getResources(), bitmap);
    107             }
    108         } else {
    109             mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
    110             mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
    111             mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
    112             mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
    113             mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
    114             final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
    115             if (bitmap != null) {
    116                 mDialogIcon = new BitmapDrawable(getResources(), bitmap);
    117             }
    118         }
    119     }
    120 
    121     @Override
    122     public void onSaveInstanceState(@NonNull Bundle outState) {
    123         super.onSaveInstanceState(outState);
    124 
    125         outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
    126         outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
    127         outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
    128         outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
    129         outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
    130         if (mDialogIcon != null) {
    131             outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
    132         }
    133     }
    134 
    135     @Override
    136     public @NonNull
    137     Dialog onCreateDialog(Bundle savedInstanceState) {
    138         final Context context = getActivity();
    139         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
    140 
    141         final AlertDialog.Builder builder = new AlertDialog.Builder(context)
    142                 .setTitle(mDialogTitle)
    143                 .setIcon(mDialogIcon)
    144                 .setPositiveButton(mPositiveButtonText, this)
    145                 .setNegativeButton(mNegativeButtonText, this);
    146 
    147         View contentView = onCreateDialogView(context);
    148         if (contentView != null) {
    149             onBindDialogView(contentView);
    150             builder.setView(contentView);
    151         } else {
    152             builder.setMessage(mDialogMessage);
    153         }
    154 
    155         onPrepareDialogBuilder(builder);
    156 
    157         // Create the dialog
    158         final Dialog dialog = builder.create();
    159         if (needInputMethod()) {
    160             requestInputMethod(dialog);
    161         }
    162 
    163         return dialog;
    164     }
    165 
    166     /**
    167      * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
    168      * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
    169      *
    170      * @return The {@link DialogPreference} associated with this
    171      * dialog.
    172      */
    173     public DialogPreference getPreference() {
    174         if (mPreference == null) {
    175             final String key = getArguments().getString(ARG_KEY);
    176             final DialogPreference.TargetFragment fragment =
    177                     (DialogPreference.TargetFragment) getTargetFragment();
    178             mPreference = (DialogPreference) fragment.findPreference(key);
    179         }
    180         return mPreference;
    181     }
    182 
    183     /**
    184      * Prepares the dialog builder to be shown when the preference is clicked.
    185      * Use this to set custom properties on the dialog.
    186      * <p>
    187      * Do not {@link AlertDialog.Builder#create()} or
    188      * {@link AlertDialog.Builder#show()}.
    189      */
    190     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {}
    191 
    192     /**
    193      * Returns whether the preference needs to display a soft input method when the dialog is
    194      * displayed. Default is false. Subclasses should override this method if they need the soft
    195      * input method brought up automatically.
    196      *
    197      * <p>Note: If your application targets P or above, ensure your subclass manually requests
    198      * focus (ideally in {@link #onBindDialogView(View)}) for the input field in order to
    199      * correctly attach the input method to the field.
    200      *
    201      * @hide
    202      */
    203     @RestrictTo(LIBRARY_GROUP)
    204     protected boolean needInputMethod() {
    205         return false;
    206     }
    207 
    208     /**
    209      * Sets the required flags on the dialog window to enable input method window to show up.
    210      */
    211     private void requestInputMethod(Dialog dialog) {
    212         Window window = dialog.getWindow();
    213         window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
    214     }
    215 
    216     /**
    217      * Creates the content view for the dialog (if a custom content view is
    218      * required). By default, it inflates the dialog layout resource if it is
    219      * set.
    220      *
    221      * @return The content View for the dialog.
    222      * @see DialogPreference#setLayoutResource(int)
    223      */
    224     protected View onCreateDialogView(Context context) {
    225         final int resId = mDialogLayoutRes;
    226         if (resId == 0) {
    227             return null;
    228         }
    229 
    230         LayoutInflater inflater = LayoutInflater.from(context);
    231         return inflater.inflate(resId, null);
    232     }
    233 
    234     /**
    235      * Binds views in the content View of the dialog to data.
    236      * <p>
    237      * Make sure to call through to the superclass implementation.
    238      *
    239      * @param view The content View of the dialog, if it is custom.
    240      */
    241     protected void onBindDialogView(View view) {
    242         View dialogMessageView = view.findViewById(android.R.id.message);
    243 
    244         if (dialogMessageView != null) {
    245             final CharSequence message = mDialogMessage;
    246             int newVisibility = View.GONE;
    247 
    248             if (!TextUtils.isEmpty(message)) {
    249                 if (dialogMessageView instanceof TextView) {
    250                     ((TextView) dialogMessageView).setText(message);
    251                 }
    252 
    253                 newVisibility = View.VISIBLE;
    254             }
    255 
    256             if (dialogMessageView.getVisibility() != newVisibility) {
    257                 dialogMessageView.setVisibility(newVisibility);
    258             }
    259         }
    260     }
    261 
    262     @Override
    263     public void onClick(DialogInterface dialog, int which) {
    264         mWhichButtonClicked = which;
    265     }
    266 
    267     @Override
    268     public void onDismiss(DialogInterface dialog) {
    269         super.onDismiss(dialog);
    270         onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
    271     }
    272 
    273     public abstract void onDialogClosed(boolean positiveResult);
    274 }
    275