de.mrapp.android.preference.activity.PreferenceActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.mrapp.android.preference.activity.PreferenceActivity.java

Source

/*
 * Copyright 2014 - 2017 Michael Rapp
 *
 * 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 de.mrapp.android.preference.activity;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.XmlRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.CardView;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;
import java.util.Set;

import de.mrapp.android.preference.activity.adapter.AdapterListener;
import de.mrapp.android.preference.activity.adapter.PreferenceHeaderAdapter;
import de.mrapp.android.preference.activity.fragment.FragmentListener;
import de.mrapp.android.preference.activity.fragment.PreferenceHeaderFragment;
import de.mrapp.android.preference.activity.parser.PreferenceHeaderParser;
import de.mrapp.android.preference.activity.view.ToolbarLarge;
import de.mrapp.android.util.ElevationUtil;
import de.mrapp.android.util.ElevationUtil.Orientation;
import de.mrapp.android.util.ViewUtil;
import de.mrapp.android.util.view.ElevationShadowView;

import static de.mrapp.android.util.Condition.ensureAtLeast;
import static de.mrapp.android.util.Condition.ensureAtMaximum;
import static de.mrapp.android.util.Condition.ensureGreater;
import static de.mrapp.android.util.Condition.ensureNotNull;
import static de.mrapp.android.util.DisplayUtil.dpToPixels;
import static de.mrapp.android.util.DisplayUtil.pixelsToDp;
import static de.mrapp.android.util.ElevationUtil.createElevationShadow;

/**
 * An activity, which provides a navigation for multiple groups of preferences, in which each group
 * is represented by an instance of the class {@link PreferenceHeader}. On devices with small
 * screens, e.g. on smartphones, the navigation is designed to use the whole available space and
 * selecting an item causes the corresponding preferences to be shown full screen as well. On
 * devices with large screens, e.g. on tablets, the navigation and the preferences of the currently
 * selected item are shown split screen.
 *
 * @author Michael Rapp
 * @since 1.0.0
 */
public abstract class PreferenceActivity extends AppCompatActivity
        implements FragmentListener, OnItemClickListener, AdapterListener {

    /**
     * When starting this activity, the invoking intent can contain this extra string to specify
     * which fragment should be initially displayed.
     */
    public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_FRAGMENT</code>, this extra can also
     * be specified to supply a bundle of arguments to pass to that fragment when it is instantiated
     * during the initial creation of the activity.
     */
    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_FRAGMENT</code>, this extra can also
     * be specified to supply the title to be shown for that fragment.
     */
    public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_FRAGMENT</code>, this extra can also
     * be specified to supply the short title to be shown for that fragment.
     */
    public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE = ":android:show_fragment_short_title";

    /**
     * When starting this activity, the invoking intent can contain this extra boolean that the
     * header list should not be displayed. This is most often used in conjunction with
     * <code>EXTRA_SHOW_FRAGMENT</code> to launch the activity to display a specific fragment that
     * the user has navigated to.
     */
    public static final String EXTRA_NO_HEADERS = ":android:no_headers";

    /**
     * When starting this activity, the invoking intent can contain this extra boolean that the
     * toolbar, which is used to show the title of the currently selected preference header, should
     * not be displayed.
     */
    public static final String EXTRA_NO_BREAD_CRUMBS = ":extra_prefs_no_bread_crumbs";

    /**
     * When starting this activity, the invoking intent can contain this extra boolean to display
     * back and next buttons in order to use the activity as a wizard.
     */
    public static final String EXTRA_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_BUTTON_BAR</code>, this extra can also
     * be specified to supply a custom text for the next button.
     */
    public static final String EXTRA_NEXT_BUTTON_TEXT = "extra_prefs_set_next_text";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_BUTTON_BAR</code>, this extra can also
     * be specified to supply a custom text for the back button.
     */
    public static final String EXTRA_BACK_BUTTON_TEXT = "extra_prefs_set_back_text";

    /**
     * When starting this activity and using <code>EXTRA_SHOW_BUTTON_BAR</code>, this extra can also
     * be specified to supply a custom text for the back button when the last preference header is
     * shown.
     */
    public static final String EXTRA_FINISH_BUTTON_TEXT = "extra_prefs_set_finish_text";

    /**
     * When starting this activity using <code>EXTRA_SHOW_BUTTON_BAR</code>, this boolean extra can
     * also used to specify, whether the number of the currently shown wizard step and the number of
     * total steps should be shown as the bread crumb title.
     */
    public static final String EXTRA_SHOW_PROGRESS = "extra_prefs_show_progress";

    /**
     * When starting this activity using <code>EXTRA_SHOW_BUTTON_BAR</code> and
     * <code>EXTRA_SHOW_PROGRESS</code>, this string extra can also be specified to supply a custom
     * format for showing the progress. The string must be formatted according to the following
     * syntax: "*%d*%d*%s*"
     */
    public static final String EXTRA_PROGRESS_FORMAT = "extra_prefs_progress_format";

    /**
     * The name of the extra, which is used to save the parameters, which have been passed when the
     * currently shown fragment has been created, within a bundle.
     */
    @VisibleForTesting
    protected static final String CURRENT_BUNDLE_EXTRA = PreferenceActivity.class.getSimpleName()
            + "::CurrentBundle";

    /**
     * The name of the extra, which is used to save the title, which is currently used by the bread
     * crumb, within a bundle.
     */
    @VisibleForTesting
    protected static final String CURRENT_TITLE_EXTRA = PreferenceActivity.class.getSimpleName() + "::CurrentTitle";

    /**
     * The name of the extra, which is used to save the short title, which is currently used by the
     * bread crumb, within a bundle.
     */
    @VisibleForTesting
    protected static final String CURRENT_SHORT_TITLE_EXTRA = PreferenceActivity.class.getSimpleName()
            + "::CurrentShortTitle";

    /**
     * The name of the extra, which is used to save the currently selected preference header, within
     * a bundle.
     */
    @VisibleForTesting
    protected static final String CURRENT_PREFERENCE_HEADER_EXTRA = PreferenceActivity.class.getSimpleName()
            + "::CurrentPreferenceHeader";

    /**
     * The name of the extra, which is used to saved the preference headers within a bundle.
     */
    @VisibleForTesting
    protected static final String PREFERENCE_HEADERS_EXTRA = PreferenceActivity.class.getSimpleName()
            + "::PreferenceHeaders";

    /**
     * The name of the extra, which is used to save the fragment, which is currently shown as the
     * preference screen, within a bundle.
     */
    private static final String PREFERENCE_SCREEN_FRAGMENT_EXTRA = PreferenceActivity.class.getSimpleName()
            + "::PreferenceScreenFragment";

    /**
     * The saved instance state, which has been passed to the activity, when it has been created.
     */
    private Bundle savedInstanceState;

    /**
     * The fragment, which has been shown as the preference screen before the activity has been
     * recreated or null, if no preference header has been previously selected.
     */
    private Fragment restoredPreferenceScreenFragment;

    /**
     * The activity's main toolbar.
     */
    private Toolbar toolbar;

    /**
     * The view, which is used to visualize a large toolbar on devices with a large screen.
     */
    private ToolbarLarge toolbarLarge;

    /**
     * The fragment, which contains the preference headers and provides the navigation to each
     * header's fragment.
     */
    private PreferenceHeaderFragment preferenceHeaderFragment;

    /**
     * The fragment, which is currently shown as the preference screen or null, if no preference
     * header is currently selected.
     */
    private Fragment preferenceScreenFragment;

    /**
     * The frame layout, which contains the activity's views. It is the activity's root view.
     */
    private FrameLayout frameLayout;

    /**
     * The parent view of the fragment, which provides the navigation to each preference header's
     * fragment.
     */
    private ViewGroup preferenceHeaderParentView;

    /**
     * The parent view of the fragment, which is used to show the preferences of the currently
     * selected preference header on devices with a large screen.
     */
    private ViewGroup preferenceScreenParentView;

    /**
     * The card view, which contains all views, e.g. the preferences themselves and the bread crumb,
     * which are shown when a preference header is selected on devices with a large screen.
     */
    private CardView preferenceScreenContainer;

    /**
     * The toolbar, which is used to show the bread crumb of the currently selected preference
     * header on devices with a large screen.
     */
    private Toolbar breadCrumbToolbar;

    /**
     * The view, which is used to draw a shadow below the toolbar, which is used to show the bread
     * crumb of the currently selected preference header on devices with a large screen.
     */
    private ElevationShadowView breadCrumbShadowView;

    /**
     * The view group, which contains the buttons, which are shown when the activity is used as a
     * wizard.
     */
    private ViewGroup buttonBar;

    /**
     * The back button, which is shown, if the activity is used as a wizard.
     */
    private Button backButton;

    /**
     * The next button, which is shown, if the activity is used as a wizard and the last preference
     * header is currently not selected.
     */
    private Button nextButton;

    /**
     * The finish button, which is shown, if the activity is used as a wizard and the last
     * preference header is currently selected.
     */
    private Button finishButton;

    /**
     * The view, which is used to draw a shadow below the activity's toolbar.
     */
    private ElevationShadowView toolbarShadowView;

    /**
     * The view, which is used to draw a shadow above the button bar when the activity is used as a
     * wizard.
     */
    private ElevationShadowView buttonBarShadowView;

    /**
     * The preference header, which is currently selected or null, if no preference header is
     * currently selected.
     */
    private PreferenceHeader currentHeader;

    /**
     * The parameters which have been passed to the currently shown fragment or null, if no
     * parameters have been passed.
     */
    private Bundle currentBundle;

    /**
     * The title, which is currently used by the bread crumb or null, if no bread crumb is currently
     * shown.
     */
    private CharSequence currentTitle;

    /**
     * The short title, which is currently used by the bread crumb or null, if no bread crumb is
     * currently shown.
     */
    private CharSequence currentShortTitle;

    /**
     * True, if the navigation icon of the activity's toolbar should be shown by default, false
     * otherwise.
     */
    private Boolean displayHomeAsUp;

    /**
     * The default title of the activity.
     */
    private CharSequence defaultTitle;

    /**
     * True, if the behavior of the navigation icon of the activity's toolbar is overridden in order
     * to return to the navigation when a preference header is currently selected on devices with a
     * small screen.
     */
    private boolean overrideNavigationIcon;

    /**
     * The width of the parent view of the fragment, which provides navigation to each preference
     * header's fragment on devices with a large screen, in dp.
     */
    private int navigationWidth;

    /**
     * True, if the fragment, which provides navigation to each preference header's fragment on
     * devices with a large screen, is currently hidden or not.
     */
    private boolean navigationHidden;

    /**
     * The background color of the view group, which contains the views, which are shown when a
     * preference screen is selected on a device with a large screen.
     */
    private int preferenceScreenBackgroundColor;

    /**
     * The background color of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen.
     */
    private int breadCrumbBackgroundColor;

    /**
     * The elevation of the view group, which contains the views, which are shown when a preference
     * screen is selected on a device with a large screen, in dp.
     */
    private int preferenceScreenElevation;

    /**
     * True, if the progress should be shown as the bread crumb title, when the activity is used as
     * a wizard, false otherwise.
     */
    private boolean showProgress;

    /**
     * The text, which is used to format the progress, which is shown as the bread crumb title.
     */
    private String progressFormat;

    /**
     * The visibility of the toolbar, which is used to show the title of the currently selected
     * preference header.
     */
    private int breadCrumbVisibility = View.VISIBLE;

    /**
     * A set, which contains the listeners, which have been registered to be notified when the
     * currently shown preference fragment has been changed.
     */
    private Set<PreferenceFragmentListener> preferenceFragmentListeners = new LinkedHashSet<>();

    /**
     * A set, which contains the listeners, which have registered to be notified when the user
     * navigates within the activity, if it used as a wizard.
     */
    private Set<WizardListener> wizardListeners = new LinkedHashSet<>();

    /**
     * Initializes the action bar's toolbar.
     */
    private void initializeToolbar() {
        if (getSupportActionBar() != null) {
            throw new IllegalStateException("An action bar is already attached to the activity. "
                    + "Use the theme \"@style/Theme.AppCompat.NoActionBar\" or "
                    + "\"@style/Theme.AppCompat.Light.NoActionBar\" as the activity's theme");
        }

        toolbar = (Toolbar) findViewById(R.id.toolbar);

        if (isSplitScreen()) {
            toolbarLarge = (ToolbarLarge) findViewById(R.id.toolbar_large);
            toolbarLarge.setVisibility(View.VISIBLE);
        } else {
            toolbar.setVisibility(View.VISIBLE);
        }

        setSupportActionBar(toolbar);
        setTitle(getTitle());
    }

    /**
     * Initializes the preference header, which is selected by default on devices with a large
     * screen.
     */
    private void initializeSelectedPreferenceHeader() {
        if (isSplitScreen()) {
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);

            if (!getListAdapter().isEmpty()) {
                selectPreferenceHeader(0);
            }
        }
    }

    /**
     * Handles extras of the intent, which has been used to start the activity, that allow to
     * initially display a specific fragment.
     */
    private void handleInitialFragmentIntent() {
        String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
        Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
        CharSequence initialTitle = getCharSequenceFromIntent(getIntent(), EXTRA_SHOW_FRAGMENT_TITLE);
        CharSequence initialShortTitle = getCharSequenceFromIntent(getIntent(), EXTRA_SHOW_FRAGMENT_SHORT_TITLE);

        if (initialFragment != null) {
            for (PreferenceHeader preferenceHeader : getListAdapter().getAllItems()) {
                if (preferenceHeader.getFragment() != null
                        && preferenceHeader.getFragment().equals(initialFragment)) {
                    selectPreferenceHeader(preferenceHeader, initialArguments);

                    if (initialTitle != null) {
                        showBreadCrumb(initialTitle, initialShortTitle);
                    }
                }
            }
        }
    }

    /**
     * Handles extras of the intent, which has been used to start the activity, that allow to show
     * the button bar in order to use the activity as a wizard.
     */
    private void handleShowButtonBarIntent() {
        boolean showButtonBar = getIntent().getBooleanExtra(EXTRA_SHOW_BUTTON_BAR, false);
        CharSequence nextButtonText = getCharSequenceFromIntent(getIntent(), EXTRA_NEXT_BUTTON_TEXT);
        CharSequence backButtonText = getCharSequenceFromIntent(getIntent(), EXTRA_BACK_BUTTON_TEXT);
        CharSequence finishButtonText = getCharSequenceFromIntent(getIntent(), EXTRA_FINISH_BUTTON_TEXT);
        CharSequence progressFormatString = getCharSequenceFromIntent(getIntent(), EXTRA_PROGRESS_FORMAT);

        if (showButtonBar) {
            showButtonBar(true);

            if (nextButtonText != null) {
                setNextButtonText(nextButtonText);
            }

            if (backButtonText != null) {
                setBackButtonText(backButtonText);
            }

            if (finishButtonText != null) {
                setFinishButtonText(finishButtonText);
            }

            showProgress(getIntent().getBooleanExtra(EXTRA_SHOW_PROGRESS, false));

            if (progressFormatString != null) {
                setProgressFormat(progressFormatString.toString());
            }
        }
    }

    /**
     * Handles the extra of the intent, which has been used to start the activity, that allows to
     * hide the navigation.
     */
    private void handleHideNavigationIntent() {
        if (getIntent().getExtras() != null && getIntent().getExtras().containsKey(EXTRA_NO_HEADERS)) {
            hideNavigation(getIntent().getExtras().getBoolean(EXTRA_NO_HEADERS));
        } else {
            hideNavigation(isNavigationHidden());
        }
    }

    /**
     * Handles the extra of the intent, which has been used to start the activity, that allows to
     * hide the bread crumbs.
     */
    private void handleHideBreadCrumbsIntent() {
        if (getIntent().getExtras() != null && getIntent().getExtras().containsKey(EXTRA_NO_BREAD_CRUMBS)) {
            setBreadCrumbVisibility(View.GONE);
        }
    }

    /**
     * Returns the char sequence, which is specified by a specific intent extra. The char sequence
     * can either be specified as a string or as a resource id.
     *
     * @param intent
     *         The intent, which specifies the char sequence, as an instance of the class {@link
     *         Intent}. The intent may not be null
     * @param name
     *         The name of the intent extra, which specifies the char sequence, as a {@link String}.
     *         The name may not be null
     * @return The char sequence, which is specified by the given intent, as an instance of the
     * class {@link CharSequence} or null, if the intent does not specify a char sequence with the
     * given name
     */
    private CharSequence getCharSequenceFromIntent(@NonNull final Intent intent, @NonNull final String name) {
        CharSequence charSequence = intent.getCharSequenceExtra(name);

        if (charSequence == null) {
            int resourceId = intent.getIntExtra(name, 0);

            if (resourceId != 0) {
                charSequence = getText(resourceId);
            }
        }

        return charSequence;
    }

    /**
     * Returns a listener, which allows to proceed to the next step, when the activity is used as a
     * wizard.
     *
     * @return The listener, which has been created, as an instance of the type {@link
     * OnClickListener}
     */
    private OnClickListener createNextButtonListener() {
        return new OnClickListener() {

            @Override
            public void onClick(final View v) {
                int currentIndex = getListAdapter().indexOf(currentHeader);

                if (currentIndex < getNumberOfPreferenceHeaders() - 1) {
                    Bundle params = notifyOnNextStep();

                    if (params != null) {
                        selectPreferenceHeader(currentIndex + 1, params);
                    }
                }
            }

        };
    }

    /**
     * Returns a listener, which allows to resume to the previous step, when the activity is used as
     * a wizard.
     *
     * @return The listener, which has been created, as an instance of the type {@link
     * OnClickListener}
     */
    private OnClickListener createBackButtonListener() {
        return new OnClickListener() {

            @Override
            public void onClick(final View v) {
                int currentIndex = getListAdapter().indexOf(currentHeader);

                if (currentIndex > 0) {
                    Bundle params = notifyOnPreviousStep();

                    if (params != null) {
                        selectPreferenceHeader(currentIndex - 1, params);
                    }
                }
            }

        };
    }

    /**
     * Returns a listener, which allows to finish the last step, when the activity is used as a
     * wizard.
     *
     * @return The listener, which has been created, as an instance of the type {@link
     * OnClickListener}
     */
    private OnClickListener createFinishButtonListener() {
        return new OnClickListener() {

            @Override
            public void onClick(final View v) {
                notifyOnFinish();
            }

        };
    }

    /**
     * Notifies all registered listeners that the user wants to navigate to the next step of the
     * wizard.
     *
     * @return A bundle, which may contain key-value pairs, which have been acquired in the wizard,
     * if navigating to the next step of the wizard should be allowed, as an instance of the class
     * {@link Bundle}, null otherwise
     */
    private Bundle notifyOnNextStep() {
        Bundle result = null;

        for (WizardListener listener : wizardListeners) {
            Bundle bundle = listener.onNextStep(getListAdapter().indexOf(currentHeader), currentHeader,
                    preferenceScreenFragment, currentBundle);

            if (bundle != null) {
                if (result == null) {
                    result = new Bundle();
                }

                result.putAll(bundle);
            }
        }

        return result;
    }

    /**
     * Notifies all registered listeners that the user wants to navigate to the previous step of the
     * wizard.
     *
     * @return A bundle, which may contain key-value pairs, which have been acquired in the wizard,
     * if navigating to the previous step of the wizard should be allowed, as an instance of the
     * class {@link Bundle}, null otherwise
     */
    private Bundle notifyOnPreviousStep() {
        Bundle result = null;

        for (WizardListener listener : wizardListeners) {
            Bundle bundle = listener.onPreviousStep(getListAdapter().indexOf(currentHeader), currentHeader,
                    preferenceScreenFragment, currentBundle);

            if (bundle != null) {
                if (result == null) {
                    result = new Bundle();
                }

                result.putAll(bundle);
            }
        }

        return result;
    }

    /**
     * Notifies all registered listeners that the user wants to finish the last step of the wizard.
     *
     * @return True, if finishing the wizard should be allowed, false otherwise
     */
    private boolean notifyOnFinish() {
        boolean result = true;

        for (WizardListener listener : wizardListeners) {
            result &= listener.onFinish(getListAdapter().indexOf(currentHeader), currentHeader,
                    preferenceScreenFragment, currentBundle);
        }

        return result;
    }

    /**
     * Notifies all registered listeners that the user wants to skip the wizard.
     *
     * @return True, if skipping the wizard should be allowed, false otherwise
     */
    private boolean notifyOnSkip() {
        boolean result = true;

        for (WizardListener listener : wizardListeners) {
            result &= listener.onSkip(getListAdapter().indexOf(currentHeader), currentHeader,
                    preferenceScreenFragment, currentBundle);
        }

        return result;
    }

    /**
     * Notifies all registered listeners that a preference fragment has been shown.
     */
    private void notifyOnPreferenceFragmentShown() {
        for (PreferenceFragmentListener listener : preferenceFragmentListeners) {
            listener.onPreferenceFragmentShown(getListAdapter().indexOf(currentHeader), currentHeader,
                    preferenceScreenFragment);
        }
    }

    /**
     * Nptifies all registered listeners that a preference fragment has been hidden.
     */
    private void notifyOnPreferenceFragmentHidden() {
        for (PreferenceFragmentListener listener : preferenceFragmentListeners) {
            listener.onPreferenceFragmentHidden();
        }
    }

    /**
     * Shows the fragment, which corresponds to a specific preference header.
     *
     * @param preferenceHeader
     *         The preference header, the fragment, which should be shown, corresponds to, as an
     *         instance of the class {@link PreferenceHeader}. The preference header may not be
     *         null
     * @param parameters
     *         The parameters, which should be passed to the fragment, as an instance of the class
     *         {@link Bundle} or null, if the preference header's extras should be used instead
     * @param forceTransition
     *         True, if instantiating a new fragment should be enforced, even if a fragment instance
     *         of the same class is already shown, false otherwise. Must be true for transitions,
     *         which have been initiated manually by the user in order to support using the same
     *         fragment class for multiple preference headers
     */
    private void showPreferenceScreen(@NonNull final PreferenceHeader preferenceHeader,
            @Nullable final Bundle parameters, final boolean forceTransition) {
        if (parameters != null && preferenceHeader.getExtras() != null) {
            parameters.putAll(preferenceHeader.getExtras());
        }

        showPreferenceScreen(preferenceHeader, parameters, true, forceTransition);
    }

    /**
     * Shows the fragment, which corresponds to a specific preference header.
     *
     * @param preferenceHeader
     *         The preference header, the fragment, which should be shown, corresponds to, as an
     *         instance of the class {@link PreferenceHeader}. The preference header may not be
     *         null
     * @param parameters
     *         The parameters, which should be passed to the fragment, as an instance of the class
     *         {@link Bundle} or null, if the preference header's extras should be used instead
     * @param launchIntent
     *         True, if a preference header's intent should be launched, false otherwise
     * @param forceTransition
     *         True, if instantiating a new fragment should be enforced, even if a fragment instance
     *         of the same class is already shown, false otherwise. Must be true for transitions,
     *         which have been initiated manually by the user in order to support using the same
     *         fragment class for multiple preference headers
     */
    private void showPreferenceScreen(@NonNull final PreferenceHeader preferenceHeader,
            @Nullable final Bundle parameters, final boolean launchIntent, final boolean forceTransition) {
        if (currentHeader == null || !currentHeader.equals(preferenceHeader)) {
            currentHeader = preferenceHeader;
            adaptWizardButtons();
            adaptBreadCrumbVisibility(parameters != null ? parameters : preferenceHeader.getExtras());

            if (preferenceHeader.getFragment() != null) {
                showBreadCrumb(preferenceHeader);
                currentBundle = (parameters != null) ? parameters : preferenceHeader.getExtras();
                showPreferenceScreenFragment(preferenceHeader, currentBundle, forceTransition);
            } else if (preferenceScreenFragment != null) {
                showBreadCrumb(preferenceHeader);
                removeFragment(preferenceScreenFragment);
                preferenceScreenFragment = null;
            }
        }

        if (launchIntent && preferenceHeader.getIntent() != null) {
            startActivity(preferenceHeader.getIntent());
        }
    }

    /**
     * Shows the fragment, which corresponds to a specific class name.
     *
     * @param preferenceHeader
     *         The preference header, the fragment, which should be shown, corresponds to, as an
     *         instance of the class {@link PreferenceHeader}. The preference header may not be
     *         null
     * @param params
     *         The parameters, which should be passed to the fragment, as an instance of the class
     *         {@link Bundle} or null, if the preference header's extras should be used instead
     * @param forceTransition
     *         True, if instantiating a new fragment should be enforced, even if a fragment instance
     *         of the same class is already shown, false otherwise. Must be true for transitions,
     *         which have been initiated manually by the user in order to support using the same
     *         fragment class for multiple preference headers
     */
    private void showPreferenceScreenFragment(@NonNull final PreferenceHeader preferenceHeader,
            @Nullable final Bundle params, final boolean forceTransition) {
        String fragmentName = preferenceHeader.getFragment();

        if (forceTransition || preferenceScreenFragment == null
                || !preferenceScreenFragment.getClass().getName().equals(fragmentName)) {
            preferenceScreenFragment = Fragment.instantiate(this, fragmentName, params);
        }

        if (isSplitScreen()) {
            replaceFragment(preferenceScreenFragment, R.id.preference_screen_parent, 0);
        } else {
            updateSavedInstanceState();
            replaceFragment(preferenceScreenFragment, R.id.preference_header_parent,
                    FragmentTransaction.TRANSIT_FRAGMENT_FADE);
            showToolbarNavigationIcon();
        }

        notifyOnPreferenceFragmentShown();
    }

    /**
     * Adapts the buttons which are shown, when the activity is used as a wizard, depending on the
     * currently selected preference header.
     */
    private void adaptWizardButtons() {
        if (currentHeader != null && isButtonBarShown()) {
            int index = getListAdapter().indexOf(currentHeader);
            getBackButton().setVisibility((index != 0) ? View.VISIBLE : View.GONE);
            getNextButton().setVisibility((index != getListAdapter().getCount() - 1) ? View.VISIBLE : View.GONE);
            getFinishButton().setVisibility((index == getListAdapter().getCount() - 1) ? View.VISIBLE : View.GONE);
        } else if (isButtonBarShown()) {
            getBackButton().setVisibility(View.GONE);
            getNextButton().setVisibility(View.GONE);
            getFinishButton().setVisibility(View.VISIBLE);
        }
    }

    /**
     * Adapts the visibility of the toolbar, which is used to show the title of the currently
     * selected preference header, depending on the currently selected preference header.
     *
     * @param parameters
     *         The parameters of the currently selected preference header as an instance of the
     *         class {@link Bundle} or null, if the preference header contains no parameters
     */
    private void adaptBreadCrumbVisibility(@Nullable final Bundle parameters) {
        if (parameters != null && parameters.containsKey(EXTRA_NO_BREAD_CRUMBS)) {
            boolean hideBreadCrumb = parameters.getBoolean(EXTRA_NO_BREAD_CRUMBS, false);
            adaptBreadCrumbVisibility(hideBreadCrumb ? View.GONE : View.VISIBLE);
        } else {
            adaptBreadCrumbVisibility(breadCrumbVisibility);
        }
    }

    /**
     * Adapts the visibility of the toolbar, which is used to show the title of the currently
     * selected preference header.
     *
     * @param visibility
     *         The visibility, which should be set, as an {@link Integer} value. The visibility may
     *         either be <code>View.VISIBLE</code>, <code>View.INVISIBLE</code> or
     *         <code>View.GONE</code>
     */
    private void adaptBreadCrumbVisibility(final int visibility) {
        if (isSplitScreen()) {
            breadCrumbToolbar.setVisibility(visibility);
            breadCrumbShadowView.setVisibility(visibility);
        } else {
            toolbar.setVisibility(visibility);
            toolbarShadowView.setVisibility(visibility);
        }
    }

    /**
     * Adapts the GUI, depending on whether the navigation is currently hidden or not.
     *
     * @param navigationHidden
     *         True, if the navigation is currently hidden, false otherwise
     */
    private void adaptNavigation(final boolean navigationHidden) {
        if (isSplitScreen()) {
            getPreferenceHeaderParentView().setVisibility(navigationHidden ? View.GONE : View.VISIBLE);
            FrameLayout.LayoutParams preferenceScreenLayoutParams = (FrameLayout.LayoutParams) getPreferenceScreenContainer()
                    .getLayoutParams();
            preferenceScreenLayoutParams.leftMargin = (navigationHidden
                    ? getResources().getDimensionPixelSize(R.dimen.preference_screen_horizontal_margin)
                    : dpToPixels(this, getNavigationWidth()))
                    - getResources().getDimensionPixelSize(R.dimen.card_view_intrinsic_margin);
            preferenceScreenLayoutParams.rightMargin = getResources()
                    .getDimensionPixelSize(navigationHidden ? R.dimen.preference_screen_horizontal_margin
                            : R.dimen.preference_screen_margin_right)
                    - getResources().getDimensionPixelSize(R.dimen.card_view_intrinsic_margin);
            preferenceScreenLayoutParams.gravity = navigationHidden ? Gravity.CENTER_HORIZONTAL
                    : Gravity.NO_GRAVITY;
            getPreferenceScreenContainer().requestLayout();
            toolbarLarge.hideNavigation(navigationHidden);
        } else {
            if (isPreferenceHeaderSelected()) {
                if (navigationHidden) {
                    hideToolbarNavigationIcon();
                } else {
                    showToolbarNavigationIcon();
                }
            } else if (navigationHidden) {
                if (getListAdapter() != null && !getListAdapter().isEmpty()) {
                    showPreferenceScreen(getListAdapter().getItem(0), null, false);
                } else if (getListAdapter() != null) {
                    finish();
                }
            }
        }
    }

    /**
     * Shows the fragment, which provides the navigation to each preference header's fragment.
     */
    private void showPreferenceHeaders() {
        notifyOnPreferenceFragmentHidden();
        int transition = 0;

        if (isPreferenceHeaderSelected()) {
            transition = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
            currentHeader = null;
            preferenceScreenFragment = null;
        }

        adaptBreadCrumbVisibility(breadCrumbVisibility);
        replaceFragment(preferenceHeaderFragment, R.id.preference_header_parent, transition);
    }

    /**
     * Replaces the fragment, which is currently contained by a specific parent view, by an other
     * fragment.
     *
     * @param fragment
     *         The fragment, which should replace the current fragment, as an instance of the class
     *         {@link Fragment}. The fragment may not be null
     * @param parentViewId
     *         The id of the parent view, which contains the fragment, that should be replaced, as
     *         an {@link Integer} value
     * @param transition
     *         The transition, which should be shown when replacing the fragment, as an {@link
     *         Integer} value or 0, if no transition should be shown
     */
    private void replaceFragment(@NonNull final Fragment fragment, final int parentViewId, final int transition) {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction.setTransition(transition);
        transaction.replace(parentViewId, fragment);
        transaction.commit();
    }

    /**
     * Removes a specific fragment from its parent view.
     *
     * @param fragment
     *         The fragment, which should be removed, as an instance of the class {@link Fragment}.
     *         The fragment may not be null
     */
    private void removeFragment(@NonNull final Fragment fragment) {
        notifyOnPreferenceFragmentHidden();
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction.remove(fragment);
        transaction.commit();
    }

    /**
     * Shows the navigation icon of the activity's toolbar.
     */
    private void showToolbarNavigationIcon() {
        if (isPreferenceHeaderSelected() && isNavigationIconOverridden() && !isNavigationHidden()
                && !(!isSplitScreen() && isButtonBarShown())) {
            if (displayHomeAsUp == null) {
                displayHomeAsUp = isDisplayHomeAsUpEnabled();
            }

            ActionBar actionBar = getSupportActionBar();

            if (actionBar != null) {
                actionBar.setDisplayHomeAsUpEnabled(true);
            }
        }
    }

    /**
     * Hides the navigation icon of the activity's toolbar, respectively sets it to the previous
     * icon.
     */
    private void hideToolbarNavigationIcon() {
        if (displayHomeAsUp != null && !displayHomeAsUp) {
            ActionBar actionBar = getSupportActionBar();

            if (actionBar != null) {
                actionBar.setDisplayHomeAsUpEnabled(false);
                actionBar.setHomeButtonEnabled(false);
            }
        }
    }

    /**
     * Returns, whether the navigation icon of the activity's toolbar is currently shown, or not.
     *
     * @return True, if the navigation icon of the activity's toolbar is currently shown, false
     * otherwise
     */
    private boolean isDisplayHomeAsUpEnabled() {
        return getSupportActionBar() != null && (getSupportActionBar().getDisplayOptions()
                & ActionBar.DISPLAY_HOME_AS_UP) == ActionBar.DISPLAY_HOME_AS_UP;
    }

    /**
     * Shows the bread crumb for a specific preference header, depending on whether the device has a
     * large screen or not. On devices with a large screen the bread crumb will be shown above the
     * currently shown fragment, on devices with a small screen the bread crumb will be shown as the
     * action bar's title instead.
     *
     * @param preferenceHeader
     *         The preference header, the bread crumb should be shown for, as an instance of the
     *         class {@link PreferenceHeader}. The preference header may not be null
     */
    private void showBreadCrumb(@NonNull final PreferenceHeader preferenceHeader) {
        CharSequence title = preferenceHeader.getBreadCrumbTitle();

        if (title == null) {
            title = preferenceHeader.getTitle();
        }

        if (title == null) {
            title = getTitle();
        }

        showBreadCrumb(title, preferenceHeader.getBreadCrumbShortTitle());
    }

    /**
     * Shows the bread crumb using a specific title and short title, depending on whether the device
     * has a large screen or not. On devices with a large screen the bread crumb will be shown above
     * the currently shown fragment, on devices with a small screen the bread crumb will be shown as
     * the action bar's title instead.
     *
     * @param title
     *         The title, which should be used by the bread crumb, as an instance of the class
     *         {@link CharSequence} or null, if no title should be used
     * @param shortTitle
     *         The short title, which should be used by the bread crumb, as an instance of the class
     *         {@link CharSequence} or null, if no short title should be used
     */
    private void showBreadCrumb(@Nullable final CharSequence title, @Nullable final CharSequence shortTitle) {
        this.currentTitle = title;
        this.currentShortTitle = shortTitle;
        CharSequence breadCrumbTitle = createBreadCrumbTitle(title);

        if (isSplitScreen()) {
            breadCrumbToolbar.setTitle(breadCrumbTitle);
        } else if (breadCrumbTitle != null) {
            if (defaultTitle == null) {
                defaultTitle = getTitle();
            }

            setTitle(breadCrumbTitle);
        }
    }

    /**
     * Creates and returns the title of the bread crumb, depending on whether the activity is used
     * as a wizard and whether the progress should be shown, or not.
     *
     * @param title
     *         The title, which should be used by the bread crumb, as an instance of the class
     *         {@link CharSequence} or null, if no title should be used
     * @return The title, which has been created, as an instance of the class {@link CharSequence}
     * or null, if no title should be used
     */
    private CharSequence createBreadCrumbTitle(@Nullable final CharSequence title) {
        if (title != null) {
            String format = getProgressFormat();

            if (format != null) {
                int currentStep = getListAdapter().indexOf(currentHeader) + 1;
                int totalSteps = getListAdapter().getCount();
                return String.format(format, currentStep, totalSteps, title);
            }
        }

        return title;
    }

    /**
     * Resets the title of the activity to the default title, if it has been previously changed.
     */
    private void resetTitle() {
        if (defaultTitle != null) {
            setTitle(defaultTitle);
            defaultTitle = null;
            currentTitle = null;
            currentShortTitle = null;
        }
    }

    /**
     * Adds the preference headers, which are currently added to the activity, to the bundle, which
     * has been passed to the activity, when it has been created. If no bundle has been passed to
     * the activity, a new bundle will be created.
     */
    private void updateSavedInstanceState() {
        if (savedInstanceState == null) {
            savedInstanceState = new Bundle();
        }

        savedInstanceState.putParcelableArrayList(PREFERENCE_HEADERS_EXTRA, getListAdapter().getAllItems());
    }

    /**
     * Obtains all relevant attributes from the activity's current theme.
     */
    private void obtainStyledAttributes() {
        obtainNavigationBackground();
        obtainPreferenceScreenBackgroundColor();
        obtainBreadCrumbBackgroundColor();
        obtainWizardButtonBarBackground();
        obtainNavigationWidth();
        obtainOverrideNavigationIcon();
        obtainToolbarElevation();
        obtainBreadCrumbElevation();
        obtainWizardButtonBarElevation();
    }

    /**
     * Obtains the background of the navigation from a specific theme.
     */
    private void obtainNavigationBackground() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.navigationBackground });
        int color = typedArray.getColor(0, 0);

        if (color != 0) {
            setNavigationBackgroundColor(color);
        } else {
            int resourceId = typedArray.getResourceId(0, 0);

            if (resourceId != 0) {
                setNavigationBackground(resourceId);
            }
        }
    }

    /**
     * Obtains the background color of the preference screen from a specific theme.
     */
    private void obtainPreferenceScreenBackgroundColor() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.preferenceScreenBackground });
        int color = typedArray.getColor(0, 0);

        if (color != 0) {
            setPreferenceScreenBackgroundColor(color);
        }
    }

    /**
     * Obtains the background color of the bread crumb from a specific theme.
     */
    private void obtainBreadCrumbBackgroundColor() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.breadCrumbBackground });
        int color = typedArray.getColor(0, 0);

        if (color != 0) {
            setBreadCrumbBackgroundColor(color);
        }
    }

    /**
     * Obtains the background of the wizard button bar from a specific theme.
     */
    private void obtainWizardButtonBarBackground() {
        View wizardButtonBar = findViewById(R.id.wizard_button_bar);
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.wizardButtonBarBackground });
        int color = typedArray.getColor(0, 0);

        if (color != 0) {
            wizardButtonBar.setBackgroundColor(color);
        } else {
            int resourceId = typedArray.getResourceId(0, 0);

            if (resourceId != 0) {
                wizardButtonBar.setBackgroundResource(resourceId);
            }
        }
    }

    /**
     * Obtains the width of the navigation from a specific theme.
     */
    private void obtainNavigationWidth() {
        int defaultValue = getResources().getDimensionPixelSize(R.dimen.default_navigation_width);
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.navigationWidth });
        int width = pixelsToDp(this, typedArray.getDimensionPixelSize(0, defaultValue));

        if (width != 0) {
            setNavigationWidth(width);
        }
    }

    /**
     * Obtains, whether the behavior of the navigation icon should be overridden, or not.
     */
    private void obtainOverrideNavigationIcon() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.overrideNavigationIcon });
        overrideNavigationIcon(typedArray.getBoolean(0, true));
    }

    /**
     * Obtains the elevation of the activity's toolbar from a specific theme.
     */
    private void obtainToolbarElevation() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.toolbarElevation });
        int defaultElevation = getResources().getDimensionPixelSize(R.dimen.default_toolbar_elevation);
        int elevation = pixelsToDp(this, typedArray.getDimensionPixelSize(0, defaultElevation));
        setToolbarElevation(elevation);
    }

    /**
     * Obtains the elevation of the toolbar, which is used to show the bread crumb of the currently
     * selected preference header on devices with a large screen, from a specific theme..
     */
    private void obtainBreadCrumbElevation() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.breadCrumbElevation });
        int defaultElevation = getResources().getDimensionPixelSize(R.dimen.default_bread_crumb_elevation);
        int elevation = pixelsToDp(this, typedArray.getDimensionPixelSize(0, defaultElevation));
        setBreadCrumbElevation(elevation);
    }

    /**
     * Obtains the elevation, of the view group, which contains all views, which are shown when a
     * preference header is selected on devices with a large screen, from a specific theme.
     */
    private void obtainPreferenceScreenElevation() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.preferenceScreenElevation });
        int defaultElevation = getResources().getDimensionPixelSize(R.dimen.default_preference_screen_elevation);
        int elevation = pixelsToDp(this, typedArray.getDimensionPixelSize(0, defaultElevation));
        setPreferenceScreenElevation(elevation);
    }

    /**
     * Obtains the elevation of the button bar from a specific theme.
     */
    private void obtainWizardButtonBarElevation() {
        TypedArray typedArray = getTheme().obtainStyledAttributes(new int[] { R.attr.wizardButtonBarElevation });
        int defaultElevation = getResources().getDimensionPixelSize(R.dimen.default_button_bar_elevation);
        int elevation = pixelsToDp(this, typedArray.getDimensionPixelSize(0, defaultElevation));
        setButtonBarElevation(elevation);
    }

    /**
     * Adds a new listener, which should be notified, when the currently shown preference fragment
     * has been changed, to the activity.
     *
     * @param listener
     *         The listener, which should be added, as an instance of the type {@link
     *         PreferenceFragmentListener}. The listener may not be null
     */
    public final void addPreferenceFragmentListener(@NonNull final PreferenceFragmentListener listener) {
        ensureNotNull(listener, "The listener may not be null");
        preferenceFragmentListeners.add(listener);
    }

    /**
     * Removes a specific listener, which should not be notified, when the currently shown
     * preference fragment has been changed, anymore.
     *
     * @param listener
     *         The listener, which should be removed, as an instance of the type {@link
     *         PreferenceFragmentListener}. The listener may not be null
     */
    public final void removePreferenceFragmentListener(@NonNull final PreferenceFragmentListener listener) {
        ensureNotNull(listener, "The listener may not be null");
        preferenceFragmentListeners.remove(listener);
    }

    /**
     * Adds a new listener, which should be notified, when the user navigates within the activity,
     * if it is used as a wizard, to the activity.
     *
     * @param listener
     *         The listener, which should be added, as an instance of the type {@link
     *         WizardListener}. The listener may not be null
     */
    public final void addWizardListener(@NonNull final WizardListener listener) {
        ensureNotNull(listener, "The listener may not be null");
        wizardListeners.add(listener);
    }

    /**
     * Removes a specific listener, which should not be notified, when the user navigates within the
     * activity, if it is used as a wizard, from the activity.
     *
     * @param listener
     *         The listener, which should be removed, as an instance of the type {@link
     *         WizardListener}. The listener may not be null
     */
    public final void removeWizardListener(@NonNull final WizardListener listener) {
        ensureNotNull(listener, "The listener may not be null");
        wizardListeners.remove(listener);
    }

    /**
     * Returns the frame layout, which contains the activity's views. It is the activity's root
     * view.
     *
     * @return The frame layout, which contains the activity's views
     */
    public final FrameLayout getFrameLayout() {
        return frameLayout;
    }

    /**
     * Returns the parent view of the fragment, which provides the navigation to each preference
     * header's fragment. On devices with a small screen this parent view is also used to show a
     * preference header's fragment, when a header is currently selected.
     *
     * @return The parent view of the fragment, which provides the navigation to each preference
     * header's fragment, as an instance of the class {@link ViewGroup}. The parent view may not be
     * null
     */
    public final ViewGroup getPreferenceHeaderParentView() {
        return preferenceHeaderParentView;
    }

    /**
     * Returns the parent view of the fragment, which is used to show the preferences of the
     * currently selected preference header on devices with a large screen.
     *
     * @return The parent view of the fragment, which is used to show the preferences of the
     * currently selected preference header, as an instance of the class {@link ViewGroup} or null,
     * if the device has a small screen
     */
    public final ViewGroup getPreferenceScreenParentView() {
        return preferenceScreenParentView;
    }

    /**
     * Returns the view group, which contains all views, e.g. the preferences themselves and the
     * bread crumb, which are shown when a preference header is selected on devices with a large
     * screen.
     *
     * @return The view group, which contains all views, which are shown when a preference header is
     * selected, as an instance of the class CardView or null, if the device has a small screen
     */
    public final CardView getPreferenceScreenContainer() {
        return preferenceScreenContainer;
    }

    /**
     * Returns the toolbar, which is used to show the activity's title on devices with a large
     * screen.
     *
     * @return The toolbar, which is used to show the activity's title on devices with a large
     * screen, as an instance of the class Toolbar or null, if the device has a small screen
     */
    public final Toolbar getNavigationToolbar() {
        if (isSplitScreen()) {
            return toolbarLarge.getToolbar();
        }

        return null;
    }

    /**
     * Returns the toolbar, which is used to show the title of the currently selected preference
     * header on devices with a large screen.
     *
     * @return The toolbar, which is used to show the title of the currently selected preference
     * header on devices with a large screen, as an instance of the class Toolbar or null, if the
     * device has a small screen
     */
    public final Toolbar getBreadCrumbToolbar() {
        if (isSplitScreen()) {
            return breadCrumbToolbar;
        }

        return null;
    }

    /**
     * Returns the view group, which contains the buttons, which are shown when the activity is used
     * as a wizard.
     *
     * @return The view group, which contains the buttons, which are shown when the activity is used
     * as a wizard, as an instance of the class {@link ViewGroup} or null, if the wizard is not used
     * as a wizard
     */
    public final ViewGroup getButtonBar() {
        return buttonBar;
    }

    /**
     * Returns the next button, which is shown, when the activity is used as a wizard and the last
     * preference header is currently not selected.
     *
     * @return The next button as an instance of the class {@link Button} or null, if the activity
     * is not used as a wizard
     */
    public final Button getNextButton() {
        return nextButton;
    }

    /**
     * Returns the text of the next button, which is shown, when the activity is used as a wizard.
     *
     * @return The text of the next button as an instance of the class {@link CharSequence} or null,
     * if the activity is not used as a wizard
     */
    public final CharSequence getNextButtonText() {
        if (nextButton != null) {
            return nextButton.getText();
        }

        return null;
    }

    /**
     * Sets the text of the next button, which is shown, when the activity is used as a wizard. The
     * text is only set, if the activity is used as a wizard.
     *
     * @param text
     *         The text, which should be set, as an instance of the class {@link CharSequence}. The
     *         text may not be null
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setNextButtonText(@NonNull final CharSequence text) {
        ensureNotNull(text, "The text may not be null");

        if (nextButton != null) {
            nextButton.setText(text);
            return true;
        }

        return false;
    }

    /**
     * Sets the text of the next button, which is shown, when the activity is used as a wizard. The
     * text is only set, if the activity is used as a wizard.
     *
     * @param resourceId
     *         The resource id of the text, which should be set, as an {@link Integer} value. The
     *         resource id must correspond to a valid string resource
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setNextButtonText(@StringRes final int resourceId) {
        return setNextButtonText(getText(resourceId));
    }

    /**
     * Returns the finish button, which is shown, when the activity is used as a wizard and the last
     * preference header is currently selected.
     *
     * @return The finish button as an instance of the class {@link Button} or null, if the activity
     * is not used as a wizard
     */
    public final Button getFinishButton() {
        return finishButton;
    }

    /**
     * Sets the text of the next button, which is shown, when the activity is used as a wizard and
     * the last preference header is currently selected. The text is only set, if the activity is
     * used as a wizard.
     *
     * @param text
     *         The text, which should be set, as an instance of the class {@link CharSequence}. The
     *         text may not be null
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setFinishButtonText(@NonNull final CharSequence text) {
        ensureNotNull(text, "The text may not be null");

        if (finishButton != null) {
            finishButton.setText(text);
            return true;
        }

        return false;
    }

    /**
     * Sets the text of the next button, which is shown, when the activity is used as a wizard and
     * the last preference header is currently selected. The text is only set, if the activity is
     * used as a wizard.
     *
     * @param resourceId
     *         The resource id of the text, which should be set, as an {@link Integer} value. The
     *         resource id must correspond to a valid string resource
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setFinishButtonText(@StringRes final int resourceId) {
        return setFinishButtonText(getText(resourceId));
    }

    /**
     * Returns the text of the finish button, which is shown, when the activity is used as a wizard
     * and the last preference header is currently selected.
     *
     * @return The text of the finish button as an instance of the class {@link CharSequence} or
     * null, if the activity is not used as a wizard
     */
    public final CharSequence getFinishButtonText() {
        if (finishButton != null) {
            return finishButton.getText();
        }

        return null;
    }

    /**
     * Returns the back button, which is shown, when the activity is used as a wizard.
     *
     * @return The back button as an instance of the class {@link Button} or null, if the activity
     * is not used as a wizard
     */
    public final Button getBackButton() {
        return backButton;
    }

    /**
     * Returns the text of the back button, which is shown, when the activity is used as a wizard.
     *
     * @return The text of the back button as an instance of the class {@link CharSequence} or null,
     * if the activity is not used as a wizard
     */
    public final CharSequence getBackButtonText() {
        if (backButton != null) {
            return backButton.getText();
        }

        return null;
    }

    /**
     * Sets the text of the back button, which is shown, when the activity is used as a wizard. The
     * text is only set, if the activity is used as a wizard.
     *
     * @param text
     *         The text, which should be set, as an instance of the class {@link CharSequence}. The
     *         text may not be null
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setBackButtonText(@NonNull final CharSequence text) {
        ensureNotNull(text, "The text may not be null");

        if (backButton != null) {
            backButton.setText(text);
            return true;
        }

        return false;
    }

    /**
     * Sets the text of the back button, which is shown, when the activity is used as a wizard. The
     * text is only set, if the activity is used as a wizard.
     *
     * @param resourceId
     *         The resource id of the text, which should be set, as an {@link Integer} value. The
     *         resource id must correspond to a valid string resource
     * @return True, if the text has been set, false otherwise
     */
    public final boolean setBackButtonText(@StringRes final int resourceId) {
        return setBackButtonText(getText(resourceId));
    }

    /**
     * Returns, whether the progress is shown, if the activity is used as a wizard.
     *
     * @return True, if the progress is shown, false otherwise or if the activity is not used as a
     * wizard
     */
    public final boolean isProgressShown() {
        return isButtonBarShown() && showProgress;
    }

    /**
     * Shows or hides the progress, if the activity is used as a wizard.
     *
     * @param showProgress
     *         True, if the progress should be shown, false otherwise
     * @return True, if the progress has been shown or hidden, false otherwise
     */
    public final boolean showProgress(final boolean showProgress) {
        if (isButtonBarShown()) {
            this.showProgress = showProgress;

            if (currentHeader != null) {
                showBreadCrumb(currentHeader);
            }

            return true;
        }

        return false;
    }

    /**
     * Returns the string, which is used to format the progress, which may be shown, if the activity
     * is used as a wizard.
     *
     * @return The string, which is used to format the progress, as a {@link String} or null, if the
     * activity is not used as a wizard or if no progress is shown
     */
    public final String getProgressFormat() {
        if (isProgressShown()) {
            return progressFormat != null ? progressFormat : getString(R.string.progress_format);
        }

        return null;
    }

    /**
     * Sets the string, which should be used to format the progress, if the activity is used as a
     * wizard and the progress is shown.
     *
     * @param progressFormat
     *         The string, which should be set, as a {@link String}. The string may not be null. It
     *         must be formatted according to the following syntax: "*%d*%d*%s*"
     * @return True, if the string has been set, false otherwise
     */
    public final boolean setProgressFormat(@NonNull final String progressFormat) {
        ensureNotNull(progressFormat, "The progress format may not be null");

        if (isProgressShown()) {
            this.progressFormat = progressFormat;

            if (currentHeader != null) {
                showBreadCrumb(currentHeader);
            }

            return true;
        }

        return false;
    }

    /**
     * Sets the string, which should be used to format the progress, if the activity is used as a
     * wizard and the progress is shown.
     *
     * @param resourceId
     *         The resource id of the string, which should be set, as an {@link Integer} value. The
     *         resource id must correspond to a valid string resource. It must be formatted
     *         according to the following syntax: "*%d*%d*%s*"
     * @return True, if the string has been set, false otherwise
     */
    public final boolean setProgressFormat(@StringRes final int resourceId) {
        return setProgressFormat(getString(resourceId));
    }

    /**
     * Returns the list view, which is used to show the preference headers.
     *
     * @return The list view, which is used to show the preference header, as an instance of the
     * class {@link ListView}. The list view may not be null
     */
    public final ListView getListView() {
        return preferenceHeaderFragment.getListView();
    }

    /**
     * Returns the adapter, which provides the preference headers for visualization using the list
     * view.
     *
     * @return The adapter, which provides the preference headers for visualization using the list
     * view, as an instance of the class {@link PreferenceHeaderAdapter}. The adapter may not be
     * null
     */
    public final PreferenceHeaderAdapter getListAdapter() {
        return preferenceHeaderFragment.getListAdapter();
    }

    /**
     * Adds all preference headers, which are specified by a specific XML resource, to the
     * activity.
     *
     * @param resourceId
     *         The resource id of the XML file, which specifies the preference headers, as an {@link
     *         Integer} value. The resource id must correspond to a valid XML resource
     */
    public final void addPreferenceHeadersFromResource(@XmlRes final int resourceId) {
        getListAdapter().addAllItems(PreferenceHeaderParser.fromResource(this, resourceId));
    }

    /**
     * Adds a new preference header to the activity.
     *
     * @param preferenceHeader
     *         The preference header, which should be added, as an instance of the class {@link
     *         PreferenceHeader}. The preference header may not be null
     */
    public final void addPreferenceHeader(@NonNull final PreferenceHeader preferenceHeader) {
        getListAdapter().addItem(preferenceHeader);
    }

    /**
     * Adds all preference headers, which are contained by a specific collection, to the activity.
     *
     * @param preferenceHeaders
     *         The collection, which contains the preference headers, which should be added, as an
     *         instance of the type {@link Collection} or an empty collection, if no preference
     *         headers should be added
     */
    public final void addAllPreferenceHeaders(@NonNull final Collection<PreferenceHeader> preferenceHeaders) {
        getListAdapter().addAllItems(preferenceHeaders);
    }

    /**
     * Removes a specific preference header from the activity.
     *
     * @param preferenceHeader
     *         The preference header, which should be removed, as an instance of the class {@link
     *         PreferenceHeader}. The preference header may not be null
     * @return True, if the preference header has been removed, false otherwise
     */
    public final boolean removePreferenceHeader(@NonNull final PreferenceHeader preferenceHeader) {
        return getListAdapter().removeItem(preferenceHeader);
    }

    /**
     * Returns a collection, which contains all preference headers, which are currently added to the
     * activity.
     *
     * @return A collection, which contains all preference headers, as an instance of the type
     * {@link Collection} or an empty collection, if the activity does not contain any preference
     * headers
     */
    public final Collection<PreferenceHeader> getAllPreferenceHeaders() {
        return getListAdapter().getAllItems();
    }

    /**
     * Returns the preference header, which belongs to a specific position.
     *
     * @param position
     *         The position of the preference header, which should be returned, as an {@link
     *         Integer} value
     * @return The preference header, which belongs to the given position, as an instance of the
     * class {@link PreferenceHeader}. The preference header may not be null
     */
    public final PreferenceHeader getPreferenceHeader(final int position) {
        return getListAdapter().getItem(position);
    }

    /**
     * Returns the number of preference headers, which are currently added to the activity.
     *
     * @return The number of preference header, which are currently added to the activity, as an
     * {@link Integer} value
     */
    public final int getNumberOfPreferenceHeaders() {
        return getListAdapter().getCount();
    }

    /**
     * Removes all preference headers, which are currently added to the activity.
     */
    public final void clearPreferenceHeaders() {
        getListAdapter().clear();
    }

    /**
     * Unselects the currently selected preference header and shows the navigation on devices with a
     * small screen, if the navigation is not hidden.
     *
     * @return True, if a preference header has been unselected, false otherwise
     */
    public final boolean unselectPreferenceHeader() {
        if (!isSplitScreen() && isPreferenceHeaderSelected() && !isNavigationHidden() && !isButtonBarShown()) {
            showPreferenceHeaders();
            hideToolbarNavigationIcon();
            resetTitle();
            return true;
        }

        return false;
    }

    /**
     * Selects a specific preference header.
     *
     * @param preferenceHeader
     *         The preference header, which should be selected, as an instance of the class {@link
     *         PreferenceHeader}. The preference header may not be null. If the preference header
     *         does not belong to the activity, a {@link NoSuchElementException} will be thrown
     */
    public final void selectPreferenceHeader(@NonNull final PreferenceHeader preferenceHeader) {
        selectPreferenceHeader(preferenceHeader, null);
    }

    /**
     * Selects a specific preference header.
     *
     * @param preferenceHeader
     *         The preference header, which should be selected, as an instance of the class {@link
     *         PreferenceHeader}. The preference header may not be null. If the preference header
     *         does not belong to the activity, a {@link NoSuchElementException} will be thrown
     * @param parameters
     *         The parameters, which should be passed to the preference header's fragment, as an
     *         instance of the class {@link Bundle} or null, if the preference header's extras
     *         should be used instead
     */
    public final void selectPreferenceHeader(@NonNull final PreferenceHeader preferenceHeader,
            @Nullable final Bundle parameters) {
        ensureNotNull(preferenceHeader, "The preference header may not be null");
        int position = getListAdapter().indexOf(preferenceHeader);

        if (position == -1) {
            throw new NoSuchElementException();
        }

        selectPreferenceHeader(position, parameters);
    }

    /**
     * Selects the preference header, which belongs to a specific position.
     *
     * @param position
     *         The position of the preference header, which should be selected, as an {@link
     *         Integer} value. If the position is invalid, an {@link IndexOutOfBoundsException} will
     *         be thrown
     */
    public final void selectPreferenceHeader(final int position) {
        selectPreferenceHeader(position, null);
    }

    /**
     * Selects the preference header, which belongs to a specific position.
     *
     * @param position
     *         The position of the preference header, which should be selected, as an {@link
     *         Integer} value. If the position is invalid, an {@link IndexOutOfBoundsException} will
     *         be thrown
     * @param parameters
     *         The parameters, which should be passed to the preference header's fragment, as an
     *         instance of the class {@link Bundle} or null, if the preference header's extras
     *         should be used instead
     */
    public final void selectPreferenceHeader(final int position, @Nullable final Bundle parameters) {
        getListView().setItemChecked(position, true);
        getListView().smoothScrollToPosition(position);
        showPreferenceScreen(getListAdapter().getItem(position), parameters, true);
    }

    /**
     * Returns, whether the preference headers and the corresponding fragments are shown split
     * screen, or not.
     *
     * @return True, if the preference headers and the corresponding fragments are shown split
     * screen, false otherwise
     */
    public final boolean isSplitScreen() {
        return getPreferenceScreenParentView() != null;
    }

    /**
     * Returns, whether the fragment, which provides navigation to each preference header's
     * fragment, is currently hidden or not.
     *
     * @return True, if the fragment, which provides navigation to each preference header's fragment
     * is currently hidden, false otherwise
     */
    public final boolean isNavigationHidden() {
        return navigationHidden;
    }

    /**
     * Hides or shows the fragment, which provides navigation to each preference header's fragment.
     * When the activity is used as a wizard on devices with a small screen, the navigation is
     * always hidden.
     *
     * @param hideNavigation
     *         True, if the fragment, which provides navigation to each preference header's
     *         fragment, should be hidden, false otherwise
     */
    public final void hideNavigation(final boolean hideNavigation) {
        this.navigationHidden = hideNavigation;
        adaptNavigation(hideNavigation);
    }

    /**
     * Returns, whether the activity is used as a wizard, or not.
     *
     * @return True, if the activity is used as a wizard, false otherwise
     */
    public final boolean isButtonBarShown() {
        return buttonBar != null;
    }

    /**
     * Shows or hides the view group, which contains the buttons, which are shown when the activity
     * is used as a wizard.
     *
     * @param showButtonBar
     *         True, if the button bar should be shown, false otherwise
     */
    public final void showButtonBar(final boolean showButtonBar) {
        if (showButtonBar) {
            buttonBar = (ViewGroup) findViewById(R.id.wizard_button_bar);
            buttonBar.setVisibility(View.VISIBLE);
            buttonBarShadowView = (ElevationShadowView) findViewById(R.id.wizard_button_bar_shadow_view);
            buttonBarShadowView.setVisibility(View.VISIBLE);
            nextButton = (Button) findViewById(R.id.next_button);
            nextButton.setOnClickListener(createNextButtonListener());
            finishButton = (Button) findViewById(R.id.finish_button);
            finishButton.setOnClickListener(createFinishButtonListener());
            backButton = (Button) findViewById(R.id.back_button);
            backButton.setOnClickListener(createBackButtonListener());

            if (!isSplitScreen()) {
                adaptNavigation(true);
            }
        } else if (buttonBar != null) {
            buttonBar.setVisibility(View.GONE);
            buttonBarShadowView.setVisibility(View.GONE);
            buttonBar = null;
            buttonBarShadowView = null;
            finishButton = null;
            nextButton = null;
            backButton = null;
        }

        getListAdapter().setEnabled(!showButtonBar);
        adaptWizardButtons();
    }

    /**
     * Returns, whether a preference header is currently selected, or not.
     *
     * @return True, if a preference header is currently selected, false otherwise
     */
    public final boolean isPreferenceHeaderSelected() {
        return currentHeader != null && currentHeader.getFragment() != null;
    }

    /**
     * Returns the preference header, which is currently selected.
     *
     * @return The preference header, which is currently selected or null, if no preference header
     * is currently selected
     */
    public final PreferenceHeader getSelectedPreferenceHeader() {
        return currentHeader;
    }

    /**
     * Returns the position of the preference header, which is currently selected.
     *
     * @return The position of the preference header, which is currently selected or -1, if no
     * preference header is currently selected
     */
    public final int getSelectedPreferenceHeaderPosition() {
        return currentHeader != null ? getListAdapter().indexOf(currentHeader) : -1;
    }

    /**
     * Returns the elevation of the activity's toolbar.
     *
     * @return The elevation of the activity's toolbar in dp as an {@link Integer} value
     */
    public final int getToolbarElevation() {
        return toolbarShadowView.getShadowElevation();
    }

    /**
     * Sets the elevation of the activity's toolbar.
     *
     * @param elevation
     *         The elevation, which should be set, in dp as an {@link Integer} value. The elevation
     *         must be at least 0 and at maximum 16
     */
    public final void setToolbarElevation(final int elevation) {
        toolbarShadowView.setShadowElevation(elevation);
    }

    /**
     * Returns the elevation of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen.
     *
     * @return The elevation of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen, in dp as an {@link Integer} value
     * or -1, if the device has a small screen
     */
    public final int getBreadCrumbElevation() {
        if (isSplitScreen()) {
            return breadCrumbShadowView.getShadowElevation();
        }

        return -1;
    }

    /**
     * Sets the elevation of the toolbar, which is used to show the title of the currently selected
     * preference header on devices with a large screen. The elevation is only set on devices with a
     * large screen.
     *
     * @param elevation
     *         The elevation, which should be set, in dp as an {@link Integer} value. The elevation
     *         must be at least 0 and at maximum 16
     * @return True, if the elevation has been set, false otherwise
     */
    public final boolean setBreadCrumbElevation(final int elevation) {
        if (breadCrumbShadowView != null) {
            breadCrumbShadowView.setShadowElevation(elevation);
            return true;
        }

        return false;
    }

    /**
     * Returns the elevation of the view group, which contains all views, e.g. the preferences
     * themselves and the bread crumb, which are shown when a preference header is selected on
     * devices with a large screen.
     *
     * @return The elevation of the view group, which contains all views, which are shown when a
     * preference header is selected on devices with a large screen, in dp as an {@link Integer}
     * value or -1, if the device has a small screen
     */
    public final int getPreferenceScreenElevation() {
        if (isSplitScreen()) {
            return preferenceScreenElevation;
        }

        return -1;
    }

    /**
     * Sets the elevation of the view group, which contains all views, e.g. the preferences
     * themselves and the bread crumb, which are shown when a preference header is selected on
     * devices with a large screen. The elevation is only set on devices with a large screen.
     *
     * @param elevation
     *         The elevation, which should be set, in dp as an {@link Integer} value. The elevation
     *         must be at least 0 and at maximum 16
     * @return True, if the elevation has been set, false otherwise
     */
    public final boolean setPreferenceScreenElevation(final int elevation) {
        ensureAtLeast(elevation, 0, "The elevation must be at least 0");
        ensureAtMaximum(elevation, ElevationUtil.MAX_ELEVATION,
                "The elevation must be at least " + ElevationUtil.MAX_ELEVATION);

        if (preferenceScreenContainer != null) {
            preferenceScreenElevation = elevation;
            preferenceScreenContainer.setCardElevation(elevation);
            return true;
        }

        return false;
    }

    /**
     * Returns the elevation of the button bar, which contains the buttons, which are shown when the
     * activity is used as a wizard.
     *
     * @return The elevation of the button bar in dp as an {@link Integer} value or -1, if the
     * activity is not used as a wizard
     */
    public final int getButtonBarElevation() {
        if (isButtonBarShown()) {
            return buttonBarShadowView.getShadowElevation();
        }

        return -1;
    }

    /**
     * Sets the elevation of the button bar, which contains the buttons, which are shown when the
     * activity is used as a wizard. The elevation is only set when the activity is used as a
     * wizard.
     *
     * @param elevation
     *         The elevation, which should be set, in dp as an {@link Integer} value. The elevation
     *         must be at least 0 and at maximum 16
     * @return True, if the elevation has been set, false otherwise
     */
    public final boolean setButtonBarElevation(final int elevation) {
        Bitmap shadow = createElevationShadow(this, elevation, Orientation.TOP, true);

        if (buttonBarShadowView != null) {
            buttonBarShadowView.setShadowElevation(elevation);
            return true;
        }

        return false;
    }

    /**
     * Returns the background color of the view group, which contains all views, which are shown
     * when a preference header is selected on devices with a large screen.
     *
     * @return The background color of the view group, which contains all views, which are shown
     * when a preference header is selected, as an {@link Integer} value, or -1, if no background
     * color has been set or if the device has a small screen
     */
    public final int getPreferenceScreenBackgroundColor() {
        if (getPreferenceScreenContainer() != null) {
            return preferenceScreenBackgroundColor;
        } else {
            return -1;
        }
    }

    /**
     * Sets the background color of the view group, which contains all views, which are shown when a
     * preference header is selected. The background is only set on devices with a large screen.
     *
     * @param color
     *         The background color, which should be set, as an {@link Integer} value
     * @return True, if the background has been set, false otherwise
     */
    public final boolean setPreferenceScreenBackgroundColor(@ColorInt final int color) {
        if (getPreferenceScreenContainer() != null) {
            preferenceScreenBackgroundColor = color;
            getPreferenceScreenContainer().setCardBackgroundColor(color);
            return true;
        }

        return false;
    }

    /**
     * Returns the background color of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen.
     *
     * @return The background color of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen, as an {@link Integer} value, or
     * -1, if no background color has been set or if the device has a small screen
     */
    public final int getBreadCrumbBackgroundColor() {
        if (getBreadCrumbToolbar() != null) {
            return breadCrumbBackgroundColor;
        } else {
            return -1;
        }
    }

    /**
     * Sets the background color of the toolbar, which is used to show the title of the currently
     * selected preference header on devices with a large screen.
     *
     * @param color
     *         The background color, which should be set, as an {@link Integer} value
     * @return True, if the background has been set, false otherwise
     */
    public final boolean setBreadCrumbBackgroundColor(@ColorInt final int color) {
        if (getBreadCrumbToolbar() != null) {
            breadCrumbBackgroundColor = color;
            GradientDrawable background = (GradientDrawable) ContextCompat.getDrawable(this,
                    R.drawable.breadcrumb_background);
            background.setColor(color);
            ViewUtil.setBackground(getBreadCrumbToolbar(), background);
            return true;
        }

        return false;
    }

    /**
     * Returns the background of the parent view of the fragment, which provides navigation to each
     * preference header's fragment.
     *
     * @return The background of the parent view of the fragment, which provides navigation to each
     * preference header's fragment, as an instance of the class {@link Drawable} or null, if no
     * background has been set
     */
    public final Drawable getNavigationBackground() {
        return getPreferenceHeaderParentView().getBackground();
    }

    /**
     * Sets the background of the parent view of the fragment, which provides navigation to each
     * preference header's fragment.
     *
     * @param resourceId
     *         The resource id of the background, which should be set, as an {@link Integer} value.
     *         The resource id must correspond to a valid drawable resource
     */
    public final void setNavigationBackground(@DrawableRes final int resourceId) {
        setNavigationBackground(ContextCompat.getDrawable(this, resourceId));
    }

    /**
     * Sets the background color of the parent view of the fragment, which provides navigation to
     * each preference header's fragment.
     *
     * @param color
     *         The background color, which should be set, as an {@link Integer} value
     */
    public final void setNavigationBackgroundColor(@ColorInt final int color) {
        setNavigationBackground(new ColorDrawable(color));
    }

    /**
     * Sets the background of the parent view of the fragment, which provides navigation to each
     * preference header's fragment.
     *
     * @param drawable
     *         The background, which should be set, as an instance of the class {@link Drawable} or
     *         null, if no background should be set
     */
    public final void setNavigationBackground(@Nullable final Drawable drawable) {
        ViewUtil.setBackground(getPreferenceHeaderParentView(), drawable);
    }

    /**
     * Returns the background of the button bar, which contains the buttons, which are shown when
     * the activity is used as a wizard.
     *
     * @return The background of the button bar, which contains the buttons, which are shown when
     * the activity is used as a wizard, as an instance of the class {@link Drawable} or null, if no
     * background has been set or the activity is not used as a wizard
     */
    public final Drawable getButtonBarBackground() {
        if (getButtonBar() != null) {
            return getButtonBar().getBackground();
        } else {
            return null;
        }
    }

    /**
     * Sets the background of the button bar, which contains the buttons, which are shown when the
     * activity is used as a wizard. The background is only set when the activity is used as a
     * wizard.
     *
     * @param resourceId
     *         The resource id of the background, which should be set, as an {@link Integer} value.
     *         The resource id must correspond to a valid drawable resource
     * @return True, if the background has been set, false otherwise
     */
    public final boolean setButtonBarBackground(@DrawableRes final int resourceId) {
        return setButtonBarBackground(ContextCompat.getDrawable(this, resourceId));
    }

    /**
     * Sets the background color of the button bar, which contains the buttons, which are shown when
     * the activity is used as a wizard. The background color is only set when the activity is used
     * as a wizard.
     *
     * @param color
     *         The background color, which should be set, as an {@link Integer} value
     * @return True, if the background color has been set, false otherwise
     */
    public final boolean setButtonBarBackgroundColor(@ColorInt final int color) {
        return setButtonBarBackground(new ColorDrawable(color));
    }

    /**
     * Sets the background of the button bar, which contains the buttons, which are shown when the
     * activity is used as a wizard. The background is only set when the activity is used as a
     * wizard.
     *
     * @param drawable
     *         The background, which should be set, as an instance of the class {@link Drawable} or
     *         null, if no background should be set
     * @return True, if the background has been set, false otherwise
     */
    public final boolean setButtonBarBackground(@Nullable final Drawable drawable) {
        if (getButtonBar() != null) {
            ViewUtil.setBackground(getButtonBar(), drawable);
            return true;
        }

        return false;
    }

    /**
     * Returns the width of the parent view of the fragment, which provides navigation to each
     * preference header's fragment on devices with a large screen.
     *
     * @return The width of the parent view of the fragment, which provides navigation to each
     * preference header's fragment, in dp as an {@link Integer} value or -1, if the device has a
     * small screen
     */
    public final int getNavigationWidth() {
        if (isSplitScreen()) {
            return navigationWidth;
        }

        return -1;
    }

    /**
     * Sets the width of the parent view of the fragment, which provides navigation to each
     * preference header's fragment. The width is only set on devices with a large screen.
     *
     * @param width
     *         The width, which should be set, in dp as an {@link Integer} value. The width must be
     *         greater than 0
     * @return True, if the width has been set, false otherwise
     */
    public final boolean setNavigationWidth(final int width) {
        ensureGreater(width, 0, "The width must be greater than 0");

        if (isSplitScreen()) {
            this.navigationWidth = width;
            int pixelWidth = dpToPixels(this, width);
            int displayWidth = getResources().getDisplayMetrics().widthPixels;
            getPreferenceHeaderParentView().setPadding(0, 0, displayWidth - pixelWidth, 0);

            if (!isNavigationHidden()) {
                FrameLayout.LayoutParams preferenceScreenLayoutParams = (FrameLayout.LayoutParams) getPreferenceScreenContainer()
                        .getLayoutParams();
                preferenceScreenLayoutParams.leftMargin = pixelWidth
                        - getResources().getDimensionPixelSize(R.dimen.card_view_intrinsic_margin);
                getPreferenceScreenContainer().requestLayout();
            }

            toolbarLarge.setNavigationWidth(width);
            return true;
        }

        return false;
    }

    /**
     * Returns, whether the behavior of the navigation icon of the activity's toolbar is overridden
     * in order to return to the navigation when a preference header is currently selected on
     * devices with a small screen, or not.
     *
     * @return True, if the behavior of the navigation icon is overridden, false otherwise
     */
    public final boolean isNavigationIconOverridden() {
        return overrideNavigationIcon;
    }

    /**
     * Sets, whether the behavior of the navigation icon of the activity's toolbar should be
     * overridden in order to return to the navigation when a preference header is currently
     * selected on devices with a small screen, or not.
     *
     * @param overrideNavigationIcon
     *         True, if the behavior of the navigation icon should be overridden, false otherwise
     */
    public final void overrideNavigationIcon(final boolean overrideNavigationIcon) {
        this.overrideNavigationIcon = overrideNavigationIcon;

        if (isPreferenceHeaderSelected()) {
            if (overrideNavigationIcon) {
                showToolbarNavigationIcon();
            } else {
                hideToolbarNavigationIcon();
            }
        }
    }

    /**
     * Returns the visibility of the toolbar, which is used to show the title of the currently
     * selected preference header.
     *
     * @return The visibility of the toolbar, which is used to show the title of the currently
     * selected preference header, as an {@link Integer} value. The visibility must either be
     * <code>View.VISIBLE</code>, <code>View.INVISIBLE</code>, <code>View.GONE</code>
     */
    public final int getBreadCrumbVisibility() {
        return breadCrumbVisibility;
    }

    /**
     * Sets the visibility of the toolbar, which is used to show the title of the currently selected
     * preference header. This takes effect regardless of the device's screen size.
     *
     * @param visibility
     *         The visibility, which should be set, as an {@link Integer} value. The visibility must
     *         either be <code>View.VISIBLE</code>, <code>View.INVISIBLE</code> or
     *         <code>View.GONE</code>
     */
    public final void setBreadCrumbVisibility(final int visibility) {
        breadCrumbVisibility = visibility;
        adaptBreadCrumbVisibility(visibility);
    }

    @Override
    public final void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) {
        showPreferenceScreen(getListAdapter().getItem(position), null, true);
    }

    @Override
    public final void onPreferenceHeaderAdded(@NonNull final PreferenceHeaderAdapter adapter,
            @NonNull final PreferenceHeader preferenceHeader, final int position) {
        if (isSplitScreen()) {
            if (adapter.getCount() == 1) {
                selectPreferenceHeader(0);
            }
        } else {
            updateSavedInstanceState();
        }

        adaptWizardButtons();
    }

    @Override
    public final void onPreferenceHeaderRemoved(@NonNull final PreferenceHeaderAdapter adapter,
            @NonNull final PreferenceHeader preferenceHeader, final int position) {
        if (isSplitScreen()) {
            if (adapter.isEmpty()) {
                removeFragment(preferenceScreenFragment);
                showBreadCrumb(null, null);
                preferenceScreenFragment = null;
                currentHeader = null;
            } else {
                int selectedIndex = getListView().getCheckedItemPosition();

                if (selectedIndex == position) {
                    PreferenceHeader selectedPreferenceHeader;
                    try {
                        selectedPreferenceHeader = getListAdapter().getItem(selectedIndex);
                    } catch (IndexOutOfBoundsException e) {
                        getListView().setItemChecked(selectedIndex - 1, true);
                        selectedPreferenceHeader = getListAdapter().getItem(selectedIndex - 1);
                    }

                    showPreferenceScreen(selectedPreferenceHeader, null, false);
                } else if (selectedIndex > position) {
                    getListView().setItemChecked(selectedIndex - 1, true);
                    showPreferenceScreen(getListAdapter().getItem(selectedIndex - 1), null, false);
                }
            }
        } else {
            if (currentHeader == preferenceHeader) {
                showPreferenceHeaders();
                hideToolbarNavigationIcon();
                resetTitle();
            }

            updateSavedInstanceState();
        }

        adaptWizardButtons();
    }

    @Override
    public final void setTitle(@Nullable final CharSequence title) {
        super.setTitle(title);
        ActionBar actionBar = getSupportActionBar();

        if (isSplitScreen()) {
            toolbarLarge.setTitle(title);

            if (actionBar != null) {
                actionBar.setTitle(null);
            }
        } else {
            if (actionBar != null) {
                actionBar.setTitle(title);
            }
        }
    }

    @Override
    public final void setTitle(@StringRes final int resourceId) {
        setTitle(getText(resourceId));
    }

    @CallSuper
    @Override
    public void onFragmentCreated(@NonNull final Fragment fragment) {
        getListView().setOnItemClickListener(this);
        getListAdapter().addListener(this);

        if (savedInstanceState == null) {
            onCreatePreferenceHeaders();
            initializeSelectedPreferenceHeader();
            handleInitialFragmentIntent();

        } else {
            ArrayList<PreferenceHeader> preferenceHeaders = savedInstanceState
                    .getParcelableArrayList(PREFERENCE_HEADERS_EXTRA);

            if (preferenceHeaders != null) {
                getListAdapter().addAllItems(preferenceHeaders);
            }

            initializeSelectedPreferenceHeader();
        }

        handleShowButtonBarIntent();
        handleHideNavigationIntent();
        handleHideBreadCrumbsIntent();
    }

    @CallSuper
    @Override
    public boolean onKeyDown(final int keyCode, final KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (unselectPreferenceHeader()) {
                return true;
            } else if (isButtonBarShown()) {
                return !notifyOnSkip() || super.onKeyDown(keyCode, event);
            }
        }

        return super.onKeyDown(keyCode, event);
    }

    @CallSuper
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            if (!isSplitScreen() && isPreferenceHeaderSelected() && isNavigationIconOverridden()
                    && !isNavigationHidden() && !isButtonBarShown()) {
                showPreferenceHeaders();
                hideToolbarNavigationIcon();
                resetTitle();
                return true;
            } else if (isButtonBarShown()) {
                return !notifyOnSkip() || super.onOptionsItemSelected(item);
            }
        }

        return super.onOptionsItemSelected(item);
    }

    @CallSuper
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.savedInstanceState = savedInstanceState;
        setContentView(R.layout.preference_activity);
        frameLayout = (FrameLayout) findViewById(R.id.preference_activity_frame_layout);
        preferenceHeaderParentView = (ViewGroup) findViewById(R.id.preference_header_parent);
        preferenceScreenParentView = (ViewGroup) findViewById(R.id.preference_screen_parent);
        preferenceScreenContainer = (CardView) findViewById(R.id.preference_screen_container);
        toolbarShadowView = (ElevationShadowView) findViewById(R.id.toolbar_shadow_view);

        if (isSplitScreen()) {
            breadCrumbToolbar = (Toolbar) findViewById(R.id.bread_crumb_toolbar);
            breadCrumbShadowView = (ElevationShadowView) findViewById(R.id.bread_crumb_shadow_view);
        }

        preferenceHeaderFragment = new PreferenceHeaderFragment();
        preferenceHeaderFragment.addFragmentListener(this);
        initializeToolbar();

        obtainStyledAttributes();
        overrideNavigationIcon(true);

        if (savedInstanceState != null) {
            restoredPreferenceScreenFragment = getFragmentManager().getFragment(savedInstanceState,
                    PREFERENCE_SCREEN_FRAGMENT_EXTRA);
        }

        showPreferenceHeaders();
    }

    @CallSuper
    @Override
    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
        Bundle bundle = savedInstanceState.getBundle(CURRENT_BUNDLE_EXTRA);
        CharSequence title = savedInstanceState.getCharSequence(CURRENT_TITLE_EXTRA);
        CharSequence shortTitle = savedInstanceState.getCharSequence(CURRENT_SHORT_TITLE_EXTRA);
        PreferenceHeader currentPreferenceHeader = savedInstanceState
                .getParcelable(CURRENT_PREFERENCE_HEADER_EXTRA);

        if (currentPreferenceHeader != null) {
            preferenceScreenFragment = restoredPreferenceScreenFragment;
            showPreferenceScreen(currentPreferenceHeader, bundle, false);
            showBreadCrumb(title, shortTitle);

            if (isSplitScreen()) {
                int selectedIndex = getListAdapter().indexOf(currentPreferenceHeader);

                if (selectedIndex != -1) {
                    getListView().setItemChecked(selectedIndex, true);
                }
            }
        } else {
            showPreferenceHeaders();
        }
    }

    @CallSuper
    @Override
    protected void onSaveInstanceState(final Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBundle(CURRENT_BUNDLE_EXTRA, currentBundle);
        outState.putCharSequence(CURRENT_TITLE_EXTRA, currentTitle);
        outState.putCharSequence(CURRENT_SHORT_TITLE_EXTRA, currentShortTitle);
        outState.putParcelable(CURRENT_PREFERENCE_HEADER_EXTRA, currentHeader);
        outState.putParcelableArrayList(PREFERENCE_HEADERS_EXTRA, getListAdapter().getAllItems());

        if (preferenceScreenFragment != null && preferenceScreenFragment.isAdded()) {
            getFragmentManager().putFragment(outState, PREFERENCE_SCREEN_FRAGMENT_EXTRA, preferenceScreenFragment);
        }
    }

    /**
     * The method, which is invoked, when the preference headers should be created. This method may
     * be overridden by implementing subclasses to add the preference headers at the activity's
     * start.
     */
    protected void onCreatePreferenceHeaders() {

    }

}