Stash dialog info when constructing pref dialogs

Due to irregularities with fragment recreation order, we can't
count on the preference fragment being initialized before the
dialog. Thus, we need to store all the information across config
changes to avoid trying to access the preference object itself
before it's available.

Change-Id: I61327b5e96c1dbecbd7546ebed6967963e869687
diff --git a/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
index 498b874..4f64876 100644
--- a/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
@@ -17,14 +17,19 @@
 package android.support.v14.preference;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v7.preference.EditTextPreference;
 import android.view.View;
 import android.widget.EditText;
 
 public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
 
+    private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
     private EditText mEditText;
 
+    private CharSequence mText;
+
     public static EditTextPreferenceDialogFragment newInstance(String key) {
         final EditTextPreferenceDialogFragment
                 fragment = new EditTextPreferenceDialogFragment();
@@ -35,6 +40,22 @@
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            mText = getEditTextPreference().getText();
+        } else {
+            mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(SAVE_STATE_TEXT, mText);
+    }
+
+    @Override
     protected void onBindDialogView(View view) {
         super.onBindDialogView(view);
 
@@ -45,7 +66,7 @@
                     " @android:id/edit");
         }
 
-        mEditText.setText(getEditTextPreference().getText());
+        mEditText.setText(mText);
     }
 
     private EditTextPreference getEditTextPreference() {
diff --git a/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
index c4e922c..6b385d8 100644
--- a/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
@@ -19,11 +19,19 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v7.preference.ListPreference;
 
 public class ListPreferenceDialogFragment extends PreferenceDialogFragment {
 
+    private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
+    private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "ListPreferenceDialogFragment.entryValues";
+
     private int mClickedDialogEntryIndex;
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
 
     public static ListPreferenceDialogFragment newInstance(String key) {
         final ListPreferenceDialogFragment fragment = new ListPreferenceDialogFragment();
@@ -33,6 +41,35 @@
         return fragment;
     }
 
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            final ListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "ListPreference requires an entries array and an entryValues array.");
+            }
+
+            mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
     private ListPreference getListPreference() {
         return (ListPreference) getPreference();
     }
@@ -41,15 +78,7 @@
     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
         super.onPrepareDialogBuilder(builder);
 
-        final ListPreference preference = getListPreference();
-
-        if (preference.getEntries() == null || preference.getEntryValues() == null) {
-            throw new IllegalStateException(
-                    "ListPreference requires an entries array and an entryValues array.");
-        }
-
-        mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
-        builder.setSingleChoiceItems(preference.getEntries(), mClickedDialogEntryIndex,
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
                 new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int which) {
                         mClickedDialogEntryIndex = which;
@@ -75,9 +104,8 @@
     @Override
     public void onDialogClosed(boolean positiveResult) {
         final ListPreference preference = getListPreference();
-        if (positiveResult && mClickedDialogEntryIndex >= 0 &&
-                preference.getEntryValues() != null) {
-            String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+        if (positiveResult && mClickedDialogEntryIndex >= 0) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
             if (preference.callChangeListener(value)) {
                 preference.setValue(value);
             }
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
index ec46aac..5b585bc 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
@@ -19,14 +19,27 @@
 import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 
 public class MultiSelectListPreferenceDialogFragment extends PreferenceDialogFragment {
 
+    private static final String SAVE_STATE_VALUES =
+            "MultiSelectListPreferenceDialogFragment.values";
+    private static final String SAVE_STATE_CHANGED =
+            "MultiSelectListPreferenceDialogFragment.changed";
+    private static final String SAVE_STATE_ENTRIES =
+            "MultiSelectListPreferenceDialogFragment.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "MultiSelectListPreferenceDialogFragment.entryValues";
+
     private Set<String> mNewValues = new HashSet<>();
     private boolean mPreferenceChanged;
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
 
     public static MultiSelectListPreferenceDialogFragment newInstance(String key) {
         final MultiSelectListPreferenceDialogFragment fragment =
@@ -37,6 +50,42 @@
         return fragment;
     }
 
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState == null) {
+            final MultiSelectListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "MultiSelectListPreference requires an entries array and " +
+                                "an entryValues array.");
+            }
+
+            mNewValues.clear();
+            mNewValues.addAll(preference.getValues());
+            mPreferenceChanged = false;
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mNewValues.clear();
+            mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES));
+            mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false);
+            mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+            mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues));
+        outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+        outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
     private MultiSelectListPreference getListPreference() {
         return (MultiSelectListPreference) getPreference();
     }
@@ -45,29 +94,23 @@
     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
         super.onPrepareDialogBuilder(builder);
 
-        final MultiSelectListPreference preference = getListPreference();
-
-        if (preference.getEntries() == null || preference.getEntryValues() == null) {
-            throw new IllegalStateException(
-                    "MultiSelectListPreference requires an entries array and " +
-                            "an entryValues array.");
+        final int entryCount = mEntryValues.length;
+        final boolean[] checkedItems = new boolean[entryCount];
+        for (int i = 0; i < entryCount; i++) {
+            checkedItems[i] = mNewValues.contains(mEntryValues[i].toString());
         }
-
-        boolean[] checkedItems = preference.getSelectedItems();
-        builder.setMultiChoiceItems(preference.getEntries(), checkedItems,
+        builder.setMultiChoiceItems(mEntries, checkedItems,
                 new DialogInterface.OnMultiChoiceClickListener() {
                     public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                         if (isChecked) {
                             mPreferenceChanged |= mNewValues.add(
-                                    preference.getEntryValues()[which].toString());
+                                    mEntryValues[which].toString());
                         } else {
                             mPreferenceChanged |= mNewValues.remove(
-                                    preference.getEntryValues()[which].toString());
+                                    mEntryValues[which].toString());
                         }
                     }
                 });
-        mNewValues.clear();
-        mNewValues.addAll(preference.getValues());
     }
 
     @Override
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
index 8f5dd64..48b404e 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
@@ -22,7 +22,12 @@
 import android.app.Fragment;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
 import android.support.v7.preference.DialogPreference;
 import android.text.TextUtils;
@@ -32,13 +37,35 @@
 import android.view.WindowManager;
 import android.widget.TextView;
 
+/**
+ * Abstract base class which presents a dialog associated with a
+ * {@link android.support.v7.preference.DialogPreference}. Since the preference object may
+ * not be available during fragment re-creation, the necessary information for displaying the dialog
+ * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
+ * instance state. Custom subclasses should also follow this pattern.
+ */
 public abstract class PreferenceDialogFragment extends DialogFragment implements
         DialogInterface.OnClickListener {
 
     protected static final String ARG_KEY = "key";
 
+    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
+    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
+    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
+    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
+    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
+    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
+
     private DialogPreference mPreference;
 
+    private CharSequence mDialogTitle;
+    private CharSequence mPositiveButtonText;
+    private CharSequence mNegativeButtonText;
+    private CharSequence mDialogMessage;
+    private @LayoutRes int mDialogLayoutRes;
+
+    private BitmapDrawable mDialogIcon;
+
     /** Which button was clicked. */
     private int mWhichButtonClicked;
 
@@ -56,7 +83,50 @@
                 (DialogPreference.TargetFragment) rawFragment;
 
         final String key = getArguments().getString(ARG_KEY);
-        mPreference = (DialogPreference) fragment.findPreference(key);
+        if (savedInstanceState == null) {
+            mPreference = (DialogPreference) fragment.findPreference(key);
+            mDialogTitle = mPreference.getDialogTitle();
+            mPositiveButtonText = mPreference.getPositiveButtonText();
+            mNegativeButtonText = mPreference.getNegativeButtonText();
+            mDialogMessage = mPreference.getDialogMessage();
+            mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+            final Drawable icon = mPreference.getDialogIcon();
+            if (icon == null || icon instanceof BitmapDrawable) {
+                mDialogIcon = (BitmapDrawable) icon;
+            } else {
+                final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+                final Canvas canvas = new Canvas(bitmap);
+                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+                icon.draw(canvas);
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        } else {
+            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+            final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+            if (bitmap != null) {
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+        if (mDialogIcon != null) {
+            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+        }
     }
 
     @Override
@@ -66,17 +136,17 @@
         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(context)
-                .setTitle(mPreference.getDialogTitle())
-                .setIcon(mPreference.getDialogIcon())
-                .setPositiveButton(mPreference.getPositiveButtonText(), this)
-                .setNegativeButton(mPreference.getNegativeButtonText(), this);
+                .setTitle(mDialogTitle)
+                .setIcon(mDialogIcon)
+                .setPositiveButton(mPositiveButtonText, this)
+                .setNegativeButton(mNegativeButtonText, this);
 
         View contentView = onCreateDialogView(context);
         if (contentView != null) {
             onBindDialogView(contentView);
             builder.setView(contentView);
         } else {
-            builder.setMessage(mPreference.getDialogMessage());
+            builder.setMessage(mDialogMessage);
         }
 
         onPrepareDialogBuilder(builder);
@@ -92,12 +162,18 @@
 
     /**
      * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
-     * been called.
+     * been called on the {@link PreferenceFragment} which launched this dialog.
      *
      * @return The {@link DialogPreference} associated with this
      * dialog.
      */
     public DialogPreference getPreference() {
+        if (mPreference == null) {
+            final String key = getArguments().getString(ARG_KEY);
+            final DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+            mPreference = (DialogPreference) fragment.findPreference(key);
+        }
         return mPreference;
     }
 
@@ -137,7 +213,7 @@
      * @see DialogPreference#setLayoutResource(int)
      */
     protected View onCreateDialogView(Context context) {
-        final int resId = mPreference.getDialogLayoutResource();
+        final int resId = mDialogLayoutRes;
         if (resId == 0) {
             return null;
         }
@@ -157,7 +233,7 @@
         View dialogMessageView = view.findViewById(android.R.id.message);
 
         if (dialogMessageView != null) {
-            final CharSequence message = mPreference.getDialogMessage();
+            final CharSequence message = mDialogMessage;
             int newVisibility = View.GONE;
 
             if (!TextUtils.isEmpty(message)) {
diff --git a/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
index 6e7663c..2abafe7 100644
--- a/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
@@ -17,13 +17,18 @@
 package android.support.v7.preference;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.view.View;
 import android.widget.EditText;
 
 public class EditTextPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
 
+    private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
     private EditText mEditText;
 
+    private CharSequence mText;
+
     public static EditTextPreferenceDialogFragmentCompat newInstance(String key) {
         final EditTextPreferenceDialogFragmentCompat
                 fragment = new EditTextPreferenceDialogFragmentCompat();
@@ -34,6 +39,22 @@
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            mText = getEditTextPreference().getText();
+        } else {
+            mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putCharSequence(SAVE_STATE_TEXT, mText);
+    }
+
+    @Override
     protected void onBindDialogView(View view) {
         super.onBindDialogView(view);
 
@@ -44,7 +65,7 @@
                     " @android:id/edit");
         }
 
-        mEditText.setText(getEditTextPreference().getText());
+        mEditText.setText(mText);
     }
 
     private EditTextPreference getEditTextPreference() {
diff --git a/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
index 351ed0b..dda461d 100644
--- a/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
@@ -18,11 +18,21 @@
 
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v7.app.AlertDialog;
 
+import java.util.ArrayList;
+
 public class ListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
 
+    private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
+    private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries";
+    private static final String SAVE_STATE_ENTRY_VALUES =
+            "ListPreferenceDialogFragment.entryValues";
+
     private int mClickedDialogEntryIndex;
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
 
     public static ListPreferenceDialogFragmentCompat newInstance(String key) {
         final ListPreferenceDialogFragmentCompat fragment =
@@ -33,6 +43,51 @@
         return fragment;
     }
 
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            final ListPreference preference = getListPreference();
+
+            if (preference.getEntries() == null || preference.getEntryValues() == null) {
+                throw new IllegalStateException(
+                        "ListPreference requires an entries array and an entryValues array.");
+            }
+
+            mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+            mEntries = preference.getEntries();
+            mEntryValues = preference.getEntryValues();
+        } else {
+            mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+            mEntries = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRIES);
+            mEntryValues = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRY_VALUES);
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+        putCharSequenceArray(outState, SAVE_STATE_ENTRIES, mEntries);
+        putCharSequenceArray(outState, SAVE_STATE_ENTRY_VALUES, mEntryValues);
+    }
+
+    private static void putCharSequenceArray(Bundle out, String key, CharSequence[] entries) {
+        final ArrayList<String> stored = new ArrayList<>(entries.length);
+
+        for (final CharSequence cs : entries) {
+            stored.add(cs.toString());
+        }
+
+        out.putStringArrayList(key, stored);
+    }
+
+    private static CharSequence[] getCharSequenceArray(Bundle in, String key) {
+        final ArrayList<String> stored = in.getStringArrayList(key);
+
+        return stored == null ? null : stored.toArray(new CharSequence[stored.size()]);
+    }
+
     private ListPreference getListPreference() {
         return (ListPreference) getPreference();
     }
@@ -41,15 +96,7 @@
     protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
         super.onPrepareDialogBuilder(builder);
 
-        final ListPreference preference = getListPreference();
-
-        if (preference.getEntries() == null || preference.getEntryValues() == null) {
-            throw new IllegalStateException(
-                    "ListPreference requires an entries array and an entryValues array.");
-        }
-
-        mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
-        builder.setSingleChoiceItems(preference.getEntries(), mClickedDialogEntryIndex,
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
                 new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int which) {
                         mClickedDialogEntryIndex = which;
@@ -75,9 +122,8 @@
     @Override
     public void onDialogClosed(boolean positiveResult) {
         final ListPreference preference = getListPreference();
-        if (positiveResult && mClickedDialogEntryIndex >= 0 &&
-                preference.getEntryValues() != null) {
-            String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+        if (positiveResult && mClickedDialogEntryIndex >= 0) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
             if (preference.callChangeListener(value)) {
                 preference.setValue(value);
             }
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
index 6bc2df0..9b6fd5a 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
@@ -19,7 +19,12 @@
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
 import android.support.v4.app.DialogFragment;
 import android.support.v4.app.Fragment;
@@ -31,13 +36,35 @@
 import android.view.WindowManager;
 import android.widget.TextView;
 
+/**
+ * Abstract base class which presents a dialog associated with a
+ * {@link android.support.v7.preference.DialogPreference}. Since the preference object may
+ * not be available during fragment re-creation, the necessary information for displaying the dialog
+ * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
+ * instance state. Custom subclasses should also follow this pattern.
+ */
 public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements
         DialogInterface.OnClickListener {
 
     protected static final String ARG_KEY = "key";
 
+    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
+    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
+    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
+    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
+    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
+    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
+
     private DialogPreference mPreference;
 
+    private CharSequence mDialogTitle;
+    private CharSequence mPositiveButtonText;
+    private CharSequence mNegativeButtonText;
+    private CharSequence mDialogMessage;
+    private @LayoutRes int mDialogLayoutRes;
+
+    private BitmapDrawable mDialogIcon;
+
     /** Which button was clicked. */
     private int mWhichButtonClicked;
 
@@ -55,26 +82,70 @@
                 (DialogPreference.TargetFragment) rawFragment;
 
         final String key = getArguments().getString(ARG_KEY);
-        mPreference = (DialogPreference) fragment.findPreference(key);
+        if (savedInstanceState == null) {
+            mPreference = (DialogPreference) fragment.findPreference(key);
+            mDialogTitle = mPreference.getDialogTitle();
+            mPositiveButtonText = mPreference.getPositiveButtonText();
+            mNegativeButtonText = mPreference.getNegativeButtonText();
+            mDialogMessage = mPreference.getDialogMessage();
+            mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+            final Drawable icon = mPreference.getDialogIcon();
+            if (icon == null || icon instanceof BitmapDrawable) {
+                mDialogIcon = (BitmapDrawable) icon;
+            } else {
+                final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+                final Canvas canvas = new Canvas(bitmap);
+                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+                icon.draw(canvas);
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        } else {
+            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+            final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+            if (bitmap != null) {
+                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+            }
+        }
     }
 
     @Override
-    public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) {
+    public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+        if (mDialogIcon != null) {
+            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+        }
+    }
+
+    @Override
+    public @NonNull
+    Dialog onCreateDialog(Bundle savedInstanceState) {
         final Context context = getActivity();
         mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(context)
-                .setTitle(mPreference.getDialogTitle())
-                .setIcon(mPreference.getDialogIcon())
-                .setPositiveButton(mPreference.getPositiveButtonText(), this)
-                .setNegativeButton(mPreference.getNegativeButtonText(), this);
+                .setTitle(mDialogTitle)
+                .setIcon(mDialogIcon)
+                .setPositiveButton(mPositiveButtonText, this)
+                .setNegativeButton(mNegativeButtonText, this);
 
         View contentView = onCreateDialogView(context);
         if (contentView != null) {
             onBindDialogView(contentView);
             builder.setView(contentView);
         } else {
-            builder.setMessage(mPreference.getDialogMessage());
+            builder.setMessage(mDialogMessage);
         }
 
         onPrepareDialogBuilder(builder);
@@ -90,12 +161,18 @@
 
     /**
      * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
-     * been called.
+     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
      *
      * @return The {@link DialogPreference} associated with this
      * dialog.
      */
     public DialogPreference getPreference() {
+        if (mPreference == null) {
+            final String key = getArguments().getString(ARG_KEY);
+            final DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+            mPreference = (DialogPreference) fragment.findPreference(key);
+        }
         return mPreference;
     }
 
@@ -135,7 +212,7 @@
      * @see DialogPreference#setLayoutResource(int)
      */
     protected View onCreateDialogView(Context context) {
-        final int resId = mPreference.getDialogLayoutResource();
+        final int resId = mDialogLayoutRes;
         if (resId == 0) {
             return null;
         }
@@ -155,7 +232,7 @@
         View dialogMessageView = view.findViewById(android.R.id.message);
 
         if (dialogMessageView != null) {
-            final CharSequence message = mPreference.getDialogMessage();
+            final CharSequence message = mDialogMessage;
             int newVisibility = View.GONE;
 
             if (!TextUtils.isEmpty(message)) {