Add PreferenceDialogFragments for Chassis.

This is needed so that the dialog shown when selecting preferences that
launch dialogs will have the OEM controllable system theme. Classes are
 kept as-is from their support lib equivalent save the addition of
documentation, static factory methods, and minor formatting.

Very similar to ag/6204405

Bug: 140443143

Test: manual in Paint Booth app.
Change-Id: I761e03c7064519263e00a6f54b11fee88f3d6bd3
diff --git a/car-chassis-lib/Android.mk b/car-chassis-lib/Android.mk
index 5ae4041..88e6efd 100644
--- a/car-chassis-lib/Android.mk
+++ b/car-chassis-lib/Android.mk
@@ -39,6 +39,7 @@
 LOCAL_STATIC_ANDROID_LIBRARIES += \
     androidx.annotation_annotation \
     androidx-constraintlayout_constraintlayout \
+    androidx.preference_preference \
     androidx.recyclerview_recyclerview
 
 LOCAL_STATIC_JAVA_LIBRARIES += \
diff --git a/car-chassis-lib/src/com/android/car/chassis/preference/EditTextPreferenceDialogFragment.java b/car-chassis-lib/src/com/android/car/chassis/preference/EditTextPreferenceDialogFragment.java
new file mode 100644
index 0000000..b05eb5b
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/preference/EditTextPreferenceDialogFragment.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.chassis.preference;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.preference.EditTextPreference;
+
+/**
+ * Presents a dialog with an {@link EditText} associated with an {@link EditTextPreference}.
+ *
+ * <p>Note: this is borrowed as-is from androidx.preference.EditTextPreferenceDialogFragmentCompat
+ * with updates to formatting to match the project style. Automotive applications should use this
+ * implementations in order to launch the system themed platform {@link AlertDialog} instead of the
+ * one in the support library.
+ */
+public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
+
+    private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
+    private EditText mEditText;
+
+    private CharSequence mText;
+
+    /**
+     * Returns a new instance of {@link EditTextPreferenceDialogFragment} for the {@link
+     * EditTextPreference} with the given {@code key}.
+     */
+    public static EditTextPreferenceDialogFragment newInstance(String key) {
+        EditTextPreferenceDialogFragment fragment =
+                new EditTextPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @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);
+
+        mEditText = view.findViewById(android.R.id.edit);
+
+        if (mEditText == null) {
+            throw new IllegalStateException(
+                    "Dialog view must contain an EditText with id @android:id/edit");
+        }
+
+        mEditText.requestFocus();
+        mEditText.setText(mText);
+        // Place cursor at the end
+        mEditText.setSelection(mEditText.getText().length());
+    }
+
+    private EditTextPreference getEditTextPreference() {
+        return (EditTextPreference) getPreference();
+    }
+
+    @Override
+    protected boolean needInputMethod() {
+        return true;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            String value = mEditText.getText().toString();
+            if (getEditTextPreference().callChangeListener(value)) {
+                getEditTextPreference().setText(value);
+            }
+        }
+    }
+
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/preference/ListPreferenceDialogFragment.java b/car-chassis-lib/src/com/android/car/chassis/preference/ListPreferenceDialogFragment.java
new file mode 100644
index 0000000..fb0b9b2
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/preference/ListPreferenceDialogFragment.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.chassis.preference;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+
+/**
+ * Presents a dialog with a list of options associated with a {@link ListPreference}.
+ *
+ * <p>Note: this is borrowed as-is from androidx.preference.ListPreferenceDialogFragmentCompat
+ * with updates to formatting to match the project style. Automotive applications should use this
+ * implementations in order to launch the system themed platform {@link AlertDialog} instead of the
+ * one in the support library.
+ */
+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;
+
+    /**
+     * Returns a new instance of {@link ListPreferenceDialogFragment} for the {@link
+     * ListPreference} with the given {@code key}.
+     */
+    public static ListPreferenceDialogFragment newInstance(String key) {
+        ListPreferenceDialogFragment fragment = new ListPreferenceDialogFragment();
+        Bundle b = new Bundle(/* capacity= */ 1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+            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();
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+
+        builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+                (dialog, which) -> {
+                    mClickedDialogEntryIndex = which;
+
+                    // Clicking on an item simulates the positive button click, and dismisses the
+                    // dialog.
+                    ListPreferenceDialogFragment.this.onClick(dialog,
+                            DialogInterface.BUTTON_POSITIVE);
+                    dialog.dismiss();
+                });
+
+        // The typical interaction for list-based dialogs is to have click-on-an-item dismiss the
+        // dialog instead of the user having to press 'Ok'.
+        builder.setPositiveButton(null, null);
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult && mClickedDialogEntryIndex >= 0) {
+            String value = mEntryValues[mClickedDialogEntryIndex].toString();
+            ListPreference preference = getListPreference();
+            if (preference.callChangeListener(value)) {
+                preference.setValue(value);
+            }
+        }
+    }
+
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/preference/MultiSelectListPreferenceDialogFragment.java b/car-chassis-lib/src/com/android/car/chassis/preference/MultiSelectListPreferenceDialogFragment.java
new file mode 100644
index 0000000..2c40e40
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/preference/MultiSelectListPreferenceDialogFragment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.chassis.preference;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.preference.MultiSelectListPreference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Presents a dialog with a list of options associated with a {@link MultiSelectListPreference}.
+ *
+ * <p>Note: this is borrowed as-is from
+ * androidx.preference.MultiSelectListPreferenceDialogFragmentCompat with updates to formatting to
+ * match the project style. Automotive applications should use this implementations in order to
+ * launch the system themed platform {@link AlertDialog} instead of the one in the support library.
+ */
+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";
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            Set<String> mNewValues = new HashSet<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            boolean mPreferenceChanged;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            CharSequence[] mEntries;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+            CharSequence[] mEntryValues;
+
+    /**
+     * Returns a new instance of {@link MultiSelectListPreferenceDialogFragment} for the {@link
+     * MultiSelectListPreference} with the given {@code key}.
+     */
+    public static MultiSelectListPreferenceDialogFragment newInstance(String key) {
+        final MultiSelectListPreferenceDialogFragment fragment =
+                new MultiSelectListPreferenceDialogFragment();
+        final Bundle b = new Bundle(1);
+        b.putString(ARG_KEY, key);
+        fragment.setArguments(b);
+        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();
+    }
+
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+
+        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());
+        }
+        builder.setMultiChoiceItems(mEntries, checkedItems,
+                (dialog, which, isChecked) -> {
+                    if (isChecked) {
+                        mPreferenceChanged |= mNewValues.add(
+                                mEntryValues[which].toString());
+                    } else {
+                        mPreferenceChanged |= mNewValues.remove(
+                                mEntryValues[which].toString());
+                    }
+                });
+    }
+
+    @Override
+    public void onDialogClosed(boolean positiveResult) {
+        if (positiveResult && mPreferenceChanged) {
+            final MultiSelectListPreference preference = getListPreference();
+            if (preference.callChangeListener(mNewValues)) {
+                preference.setValues(mNewValues);
+            }
+        }
+        mPreferenceChanged = false;
+    }
+}
diff --git a/car-chassis-lib/src/com/android/car/chassis/preference/PreferenceDialogFragment.java b/car-chassis-lib/src/com/android/car/chassis/preference/PreferenceDialogFragment.java
new file mode 100644
index 0000000..c1f9239
--- /dev/null
+++ b/car-chassis-lib/src/com/android/car/chassis/preference/PreferenceDialogFragment.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.chassis.preference;
+
+import android.app.AlertDialog;
+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.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceFragmentCompat;
+
+/**
+ * Abstract base class which presents a dialog associated with a {@link
+ * androidx.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.
+ *
+ * <p>Note: this is borrowed as-is from androidx.preference.PreferenceDialogFragmentCompat with
+ * updates to formatting to match the project style. Automotive applications should use children of
+ * this fragment in order to launch the system themed platform {@link AlertDialog} instead of the
+ * one in the support library.
+ */
+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;
+    @LayoutRes
+    private int mDialogLayoutRes;
+
+    private BitmapDrawable mDialogIcon;
+
+    /** Which button was clicked. */
+    private int mWhichButtonClicked;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Fragment rawFragment = getTargetFragment();
+        if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
+            throw new IllegalStateException(
+                    "Target fragment must implement TargetFragment interface");
+        }
+
+        DialogPreference.TargetFragment fragment =
+                (DialogPreference.TargetFragment) rawFragment;
+
+        String key = getArguments().getString(ARG_KEY);
+        if (savedInstanceState == null) {
+            mPreference = (DialogPreference) fragment.findPreference(key);
+            mDialogTitle = mPreference.getDialogTitle();
+            mPositiveButtonText = mPreference.getPositiveButtonText();
+            mNegativeButtonText = mPreference.getNegativeButtonText();
+            mDialogMessage = mPreference.getDialogMessage();
+            mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+            Drawable icon = mPreference.getDialogIcon();
+            if (icon == null || icon instanceof BitmapDrawable) {
+                mDialogIcon = (BitmapDrawable) icon;
+            } else {
+                Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+                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);
+            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
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Context context = getActivity();
+        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .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(mDialogMessage);
+        }
+
+        onPrepareDialogBuilder(builder);
+
+        // Create the dialog
+        Dialog dialog = builder.create();
+        if (needInputMethod()) {
+            // Request input only after the dialog is shown. This is to prevent an issue where the
+            // dialog view collapsed the content on small displays.
+            dialog.setOnShowListener(d -> requestInputMethod(dialog));
+        }
+
+        return dialog;
+    }
+
+    /**
+     * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
+     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
+     *
+     * @return the {@link DialogPreference} associated with this dialog.
+     */
+    public DialogPreference getPreference() {
+        if (mPreference == null) {
+            String key = getArguments().getString(ARG_KEY);
+            DialogPreference.TargetFragment fragment =
+                    (DialogPreference.TargetFragment) getTargetFragment();
+            mPreference = (DialogPreference) fragment.findPreference(key);
+        }
+        return mPreference;
+    }
+
+    /**
+     * Prepares the dialog builder to be shown when the preference is clicked. Use this to set
+     * custom properties on the dialog.
+     *
+     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
+     */
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+    }
+
+    /**
+     * Returns whether the preference needs to display a soft input method when the dialog is
+     * displayed. Default is false. Subclasses should override this method if they need the soft
+     * input method brought up automatically.
+     *
+     * <p>Note: Ensure your subclass manually requests focus (ideally in {@link
+     * #onBindDialogView(View)}) for the input field in order to
+     * correctly attach the input method to the field.
+     */
+    protected boolean needInputMethod() {
+        return false;
+    }
+
+    /**
+     * Sets the required flags on the dialog window to enable input method window to show up.
+     */
+    private void requestInputMethod(Dialog dialog) {
+        Window window = dialog.getWindow();
+        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+    }
+
+    /**
+     * Creates the content view for the dialog (if a custom content view is required). By default,
+     * it inflates the dialog layout resource if it is set.
+     *
+     * @return the content View for the dialog.
+     * @see DialogPreference#setLayoutResource(int)
+     */
+    protected View onCreateDialogView(Context context) {
+        int resId = mDialogLayoutRes;
+        if (resId == 0) {
+            return null;
+        }
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        return inflater.inflate(resId, null);
+    }
+
+    /**
+     * Binds views in the content View of the dialog to data.
+     *
+     * <p>Make sure to call through to the superclass implementation.
+     *
+     * @param view the content View of the dialog, if it is custom.
+     */
+    @CallSuper
+    protected void onBindDialogView(View view) {
+        View dialogMessageView = view.findViewById(android.R.id.message);
+
+        if (dialogMessageView != null) {
+            CharSequence message = mDialogMessage;
+            int newVisibility = View.GONE;
+
+            if (!TextUtils.isEmpty(message)) {
+                if (dialogMessageView instanceof TextView) {
+                    ((TextView) dialogMessageView).setText(message);
+                }
+
+                newVisibility = View.VISIBLE;
+            }
+
+            if (dialogMessageView.getVisibility() != newVisibility) {
+                dialogMessageView.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        mWhichButtonClicked = which;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        super.onDismiss(dialog);
+        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
+    }
+
+    /**
+     * Called when the dialog is dismissed.
+     *
+     * @param positiveResult {@code true} if the dialog was dismissed with {@link
+     *                       DialogInterface#BUTTON_POSITIVE}.
+     */
+    protected abstract void onDialogClosed(boolean positiveResult);
+}